2017-11-09 5 views
2

Ich frage mich, ob es eine Möglichkeit gibt, über eine scheinbare Inkonsistenz in Scala die Behandlung von Function1 und Function2..N zu patchen.Scala-Typ-System und die Eingabe von FunctionN

Für eine Function1, sagen Int => String, die Parameterliste (Int) zu Int nicht identisch ist (auch wenn die beiden isomorph sind), aber der Compiler die Eingabe als bare Int (siehe Code unten) schließen.

Für Function2..N, sagen val f: (String, Int) => String = ??? Der Compiler wird keinen Typ für die Eingabeparameterliste ableiten. Insbesondere gibt es keine ParameterList [String, Int], obwohl sie isomorph zu Tupeln von (String, Int) und jedem anderen Wrapper ist, den Sie gerne in Strings und Ints einsetzen.

Meine erste Frage ist, gibt es einen Grund, warum es so ist (oder ist das etwas, das auf einer Liste von Scala Todos existiert)? Warum kann Function1 in Eingabe- und Ausgabetyp dekonstruiert werden, aber nicht in Funktion2, und möchte jemand das beheben?

Gibt es irgendwelche Arbeiten um. Insbesondere gibt es im folgenden Code eine Möglichkeit, um Invoke2 arbeiten zu lassen?

package net.jtownson.swakka 

import org.scalatest.FlatSpec 
import org.scalatest.Matchers._ 
import shapeless.ops.function._ 
import shapeless.{HList, HNil, _} 

class TypeclassOfFunctionTypeSpec extends FlatSpec { 

    // Here, we know the return type of F is constrained to be O 
    // (because that's how the shapeless FnToProduct typeclass works) 
    def invoke1[F, I <: HList, O](f: F, i: I) 
           (implicit ftp: FnToProduct.Aux[F, I => O]): O = ftp(f)(i) 

    // So let's try to express that by extracting the input type of F as FI 
    def invoke2[FI, I <: HList, O](f: FI => O, i: I) 
           (implicit ftp: FnToProduct.Aux[FI => O, I => O]): O = ftp(f)(i) 

    "Invoke" should "work for a Function1" in { 

    // Here's our function (Int) => String 
    val f: (Int) => String = (i) => s"I got $i" 

    val l = 1 :: HNil 

    // this works 
    val r1: String = invoke1(f, l) 

    // So does this. (With evidence that the compiler sees the function parameter list (Int) as just Int 
    val r2: String = invoke2[Int, Int::HNil, String](f, l) 

    r1 shouldBe "I got 1" 
    r2 shouldBe "I got 1" 
    } 

    "Invoke" should "work for a Function2" in { 

    // Here's our function (String, Int) => String 
    val f: (String, Int) => String = (s, i) => s"I got $s and $i" 

    val l = "s" :: 1 :: HNil 

    // this works 
    val r1: String = invoke1(f, l) 

    // But this does not compile. There is no expansion for the type of FI 
    // (String, Int) != the function Parameter list (String, Int) 
    val r2: String = invoke2(f, l) 
    /* 
    Error:(...) type mismatch; 
    found : (String, Int) => String 
     required: ? => String 
     val r1: String = invoke1(f, l) 
    */ 

    r1 shouldBe "I got s and 1" 
    r2 shouldBe "I got s and 1" 
    } 
} 
+0

Sind rufen Sie Kenntnis von 'Function2 # tupled' und' Function2 # Curry-Methoden? –

+0

Ja, ich bin :-) Und auch, dass Tupled nicht auf Function1 existiert. Vermutlich liegt das daran, dass die Fähigkeit, den _putput _ (als den blanken Typ) von Function1 auszudrücken, nicht notwendig ist. Schlechte alte Funktion2..N fehlt das. In diesem Sinne fühlt sich Tupel wie ein Workaround an. Ich denke aber, dass es einen guten Grund für die Diskrepanz zwischen Funktion 1 und 2 in dieser Hinsicht gibt? –

Antwort

1

Int => String ist Syntax Zucker für Function1[Int, String], (String, Int) => String Syntax Zucker ist Function2[String, Int, String] für, ((String, Int)) => String Syntax Zucker für Function1[(String, Int), String] aka Function1[Tuple2[String, Int], String] ist.

Sie können Shapeless helfen FnToProduct Instanzen zu lösen, wenn Sie

implizite Konvertierung definieren
implicit def tupledFnToProduct[FI1, FI2, O, Out0](implicit 
    ftp: FnToProduct.Aux[Function2[FI1, FI2, O], Out0] 
): FnToProduct.Aux[Function1[(FI1, FI2), O], Out0] = 
    new FnToProduct[Function1[(FI1, FI2), O]] { 
    override type Out = Out0 
    override def apply(f: Function1[(FI1, FI2), O]) = ftp((x, y) => f(x, y)) 
    } 

Dann können Sie invoke2 mit .tupled

val f: (String, Int) => String = (s, i) => s"I got $s and $i" 

val l = "s" :: 1 :: HNil 

val r2: String = invoke2(f.tupled, l) 

r2 == "I got s and 1" //true 
Verwandte Themen