9 votes

Lifting de méthodes vers des valeurs de fonctions en Scala

La bibliothèque Scala permet-elle de transformer une méthode d'un type donné en une valeur de fonction ?

Par exemple, supposons que je veuille lever String.length . Je peux écrire

val f: String => Int = _.length

ou

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

Toutefois, cette syntaxe n'est pas toujours idéale (en particulier au milieu d'une expression plus large). Je pense que je cherche quelque chose qui permette des expressions comme

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

et je pense à quelque chose comme ça :

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 : La bibliothèque standard (ou toute autre bibliothèque) propose-t-elle quelque chose de ce type ?

Question 2 : Si ce n'est pas le cas, est-il possible de l'écrire de manière à ce que Option.filter peut être levé 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 page lift J'obtiens 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 auquel je me heurte est lié à la surcharge du système d'information. lift méthode. Si je renomme les surcharges, je puede ascenseur Option.filter sans tous les paramètres de type supplémentaires.

8voto

Daniel C. Sobral Points 159554

Quel est le problème avec

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

?

8voto

Aaron Novstrup Points 10742

J'ai finalement trouvé une solution qui me convient. Cette version supporte une syntaxe simple et un point d'entrée unique à l'API, tout en offrant un contrôle sur la forme de la fonction levée (c.-à-d. non curry, partiellement curry, ou complètement curry).

Exemples :

J'utiliserai 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 consiste à renvoyer la méthode en tant que fonction partiellement appliquée, ce qui équivaut à invoquer ((_: 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 certains implicites, on peut demander des formes curries ou non curries :

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 distincte 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. ...
}

4voto

michid Points 3526

Le passage du filtre en tant que méthode partiellement appliquée semble faire l'affaire :

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>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X