2010-11-11 5 views
8

La bibliothèque Scala fournit-elle un support pour lever une méthode d'un type donné vers une valeur de fonction? Par exemple, supposons que je veuille lever String.length.Méthodes de levage pour les valeurs de fonction dans Scala

Je peux écrire

val f: String => Int = _.length 

ou

val f = { s: String => s.length } 

Cependant, cette syntaxe est pas toujours idéale (surtout au milieu d'une expression plus large). Je pense que je suis à la recherche de quelque chose qui va permettre à des expressions comme

Lift[String](_.length) 
Lift[Option[Int]].lift(_.filter) 

et j'ai à l'esprit quelque chose comme ceci:

class Lift[T] {               
    def apply[R](f: T => R): T => R = f 

    def lift[A, R](f: (T) => (A) => R): (T, A) => R = 
     f(_)(_) 
    def lift[A1, A2, R](f: (T) => (A1, A2) => R): (T, A1, A2) => R = 
     f(_)(_,_) 
    // ... etc. ... 
} 
object Lift { 
    def apply[T] = new Lift[T] 
} 

Question 1: Est-ce quela bibliothèque standard (ou une bibliothèque) fournir quelque chose comme ça?

Question 2: Sinon, est-il possible d'écrire de telle sorte que Option.filter peut être soulevé comme ci-dessus (plutôt que comme Lift[Option[Int]].lift[Int => Boolean, Option[Int]](_.filter))? Sans fournir les paramètres de type sur la méthode lift j'obtenir l'erreur suivante:

 
error: missing parameter type for expanded function ((x$1) => x$1.filter) 
     Lift[Option[Int]].lift(_.filter) 
          ^

Mise à jour:

Apparemment, le problème que je suis en cours d'exécution pour a quelque chose à voir avec la méthode lift surchargé. Si je renomme les surcharges, je peux soulever Option.filter sans tous les paramètres de type supplémentaires.

Répondre

7

J'ai finalement trouvé une solution dont je suis content. Cette version prend en charge une syntaxe simple et un point d'entrée unique pour l'API, tout en fournissant également un contrôle sur la forme de la fonction levée (c'est-à-dire sans contrainte, partiellement carnée, ou entièrement carnée).

Exemples:

Je vais utiliser la définition de classe suivante dans les exemples ci-dessous:

class Foo { 
    def m1: Int = 1 
    def m2(i: Int): Int = i 
    def m3(i: Int, j: Int): Int = i + j 
} 

La forme la plus simple de levage est de retourner la méthode en fonction partiellement appliquée, ce qui équivaut à invoquant ((_: Foo).method _):

scala> lift[Foo](_.m1)       // NOTE: trailing _ not required 
res0: (Foo) => Int = <function1> 

scala> lift[Foo](_.m2 _)      // NOTE: trailing _ required 
res1: (Foo) => (Int) => Int = <function1> 

scala> lift[Foo](_.m3 _) 
res2: (Foo) => (Int, Int) => Int = <function1> // NOTE: the result is partly curried 

En important des implicits, on peut demander cu formes rried ou uncurried:

scala> {       
    | import CurriedLiftables._ 
    | lift[Foo](_.m3 _)   
    | } 
res3: (Foo) => (Int) => (Int) => Int = <function1> 

scala> {       
    | import UncurriedLiftables._ 
    | lift[Foo](_.m3 _)   
    | } 
res4: (Foo, Int, Int) => Int = <function3> 

Mise en œuvre:

class Lift[T] { 
    def apply[R,F](f: T => R)(implicit e: (T => R) Liftable F): F = e.lift(f) 
} 
object lift { 
    def apply[T] = new Lift[T] 
} 

class Liftable[From, To](val lift: From => To) 

class DefaultLiftables { 
    implicit def lift[F]: F Liftable F = new Liftable(identity) 
} 
object Liftable extends DefaultLiftables 

class UncurriedLiftable1 extends DefaultLiftables { 
    implicit def lift1[T, A, R]: (T => A => R) Liftable ((T, A) => R) = 
     new Liftable(f => f(_)(_)) 
} 
class UncurriedLiftable2 extends UncurriedLiftable1 { 
    implicit def lift2[T, A1, A2, R]: (T => (A1, A2) => R) Liftable ((T, A1, A2) => R) = 
     new Liftable (f => f(_)(_,_)) 
} 
// UncurriedLiftable3, UncurriedLiftable4, ... 
object UncurriedLiftables extends UncurriedLiftable2 

class CurriedLiftable2 extends DefaultLiftables { 
    implicit def lift2[T, A1, A2, R]: (T => (A1, A2) => R) Liftable (T => A1 => A2 => R) = 
     new Liftable(f => (x: T) => (a1: A1) => (a2: A2) => f(x)(a1, a2)) 
} 
// CurriedLiftable3, CurriedLiftable4, ... 
object CurriedLiftables extends CurriedLiftable2 

Ma solution précédente nécessitait une méthode de levage pour chaque arité:

import Lift._ 
val f1 = lift0[String](_.length) 
val f2 = lift1[Option[Int]](_.filter) 
val f3 = lift2[Either[String, Int]](_.fold) 

Mise en œuvre:

class Lift0[T] { 
    def apply[R](f: T => R): T => R = f 
} 
class Lift1[T] { 
    def apply[A, R](f: (T) => (A) => R): (T, A) => R = 
     f(_)(_) 
} 
class Lift2[T] { 
    def apply[A1, A2, R](f: (T) => (A1, A2) => R): (T, A1, A2) => R = 
     f(_)(_,_) 
} 
// ... etc. ... 

object Lift { 
    def lift0[T] = new Lift0[T] 
    def lift1[T] = new Lift1[T] 
    def lift2[T] = new Lift2[T] 
    // ... etc. ... 
} 
4

Passant filtre méthode partiellement appliquée semble faire le travail:

scala> class Lift[T] {           
    | def apply[R](f: T => R): T => R = f 
    | } 
defined class Lift 

scala> object Lift { 
    | def apply[T] = new Lift[T] 
    | } 
defined module Lift 

scala> val ls = Lift[String](_.length) 
ls: (String) => Int = <function1> 

scala> val los = Lift[Option[Int]](_.filter _)  
los: (Option[Int]) => ((Int) => Boolean) => Option[Int] = <function1> 
+0

Cela fonctionne, mais j'espérais obtenir un '(Option [Int], (Int) => Boolean) => Option [Int]'. –

+0

Je suis venu avec une solution qui fournit une fonction de levée unique avec une syntaxe simple, mais qui donne également à l'appelant le contrôle sur la forme de la fonction levée (curry, partiellement curry, ou non). Voir http://stackoverflow.com/questions/4160239/lifting-methods-in-scala/4168990#4168990 si cela vous intéresse. –

7

Quel est le problème avec

(_: String).length 
(_: Option[Int]).filter _ 

?

+0

Cela devrait être (_: Option [Int]). Filter _ (attention au trait de soulignement final!) – michid

+0

@michid Corrigé, merci. –

+0

Pas mal. J'espère obtenir une forme complètement libre, mais je peux devoir me contenter de ceci. –

Questions connexes