2010-11-11 15 views
8

Czy biblioteka Scala zapewnia wsparcie dla podnoszenia metody danego typu do wartości funkcji?Metody podnoszenia wartości funkcji w Scali

Załóżmy na przykład, że chcę podnieść String.length. Mogę napisać

val f: String => Int = _.length 

lub

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

jednak składnia ta nie zawsze jest idealne (szczególnie pośród większej ekspresji). Myślę, że szukam czegoś, co pozwoli wyrażeń jak

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

i mam na myśli coś takiego:

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] 
} 

Pytanie 1:Czy biblioteka standardowa (lub dowolnej biblioteki) dostarczyć coś takiego?

Pytanie 2: Jeśli nie, to możliwe, aby zapisać go w taki sposób, że można podnieść Option.filter jak wyżej (zamiast jak Lift[Option[Int]].lift[Int => Boolean, Option[Int]](_.filter))? Bez podawania parametrów typu na metodzie lift pojawia się następujący błąd:

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

Aktualizacja:

Najwyraźniej problem biegnę, by coś zrobić z przeciążonej lift metody. Jeśli zmienię nazwę przeciążenia, mogę podnieść podnieść Option.filter bez wszystkich dodatkowych parametrów typu.

Odpowiedz

7

W końcu znalazłem rozwiązanie, z którego jestem zadowolony. Ta wersja obsługuje prostą składnię i pojedynczy punkt wejścia do API, jednocześnie zapewniając kontrolę nad formą zniesionej funkcji (tj. Nieuruchwytowaną, częściowo zwiniętą lub całkowicie zwiniętą w curry).

Przykłady:

będziemy używać następującą definicję klasy w przykładach poniżej:

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

Najprostszą formą podnoszenia jest powrót metody jako funkcję części, co odpowiada stosowanej powołując ((_: 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 

Poprzez import niektórych implicits, można zażądać cu rried lub uncurried formy:

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

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

Realizacja:

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 

Moje poprzednie rozwiązanie wymaga oddzielnego metodę podnoszenia dla każdego liczbę operandów:

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

Realizacja:

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

Przechodząc w filtrze jako metody stosowanej częściowo wydaje się wykonać zadanie:

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

To działa, ale miałem nadzieję uzyskać '(Option [Int], (Int) => Boolean) => Opcja [Int]'. –

+0

Wymyśliłem rozwiązanie, które zapewnia pojedynczą funkcję podnoszenia z prostą składnią, ale także daje kontrolę dzwoniącemu nad formą zniesionej funkcji (curry, częściowo curried lub uncurried). Zobacz http://stackoverflow.com/questions/4160239/lifting-methods-in-scala/4168990#4168990 jeśli jesteś zainteresowany. –

7

Jaki jest problem z

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

?

+0

To powinno być (_: Opcja [Int]). Filter _ (pamiętajcie o końcowym podkreśleniu!) – michid

+0

@michid Poprawione, dziękuję. –

+0

Nieźle. Mam nadzieję, że otrzymam w pełni niezasłużoną formę, ale być może będę musiał się z tym pogodzić. –

Powiązane problemy