2011-01-04 5 views
5

Ich folgte dem Ratschlag here, um eine Funktion namens Quadrat zu definieren, und versuchte dann, es an eine doppelt aufgerufene Funktion zu übergeben. Die Funktionen sind wie folgt definiert:Übergeben von Funktionen für alle anwendbaren Typen um

def square[T](n: T)(implicit numeric: Numeric[T]): T = numeric.times(n, n) 
def twice[T](f: (T) => T, a: T): T = f(f(a)) 

Wenn zweimal Aufruf (quadratisch, 2), spuckt die REPL eine Fehlermeldung aus:

scala> twice(square, 2) 
<console>:8: error: could not find implicit value for parameter numeric: Numeric[T] 
     twice(square, 2) 
     ^

jedermann?

Antwort

20

Ich stimme nicht mit allen hier überein, außer Andrew Phillips. Nun, alle bis jetzt. :-) Das Problem dabei ist:

def twice[T](f: (T) => T, a: T): T = f(f(a)) 

Sie erwarten, wie Neulinge oft Scala tun, für Scala-Compiler berücksichtigt beiden Parameter twice zu ergreifen, um die richtigen Typen zu schließen. Scala macht das jedoch nicht - es verwendet nur Informationen von einer Parameterliste zur nächsten, aber nicht von einem Parameter zum nächsten.Das bedeutet, dass die Parameter f und a unabhängig voneinander analysiert werden, ohne den Vorteil zu haben, zu wissen, was der andere ist.

Das bedeutet zum Beispiel, dass diese Werke:

twice(square[Int], 2) 

Nun, wenn Sie brechen sie in zwei Parameterlisten, dann funktioniert es auch:

def twice[T](a: T)(f: (T) => T): T = f(f(a)) 
twice(2)(square) 

Also, im Grunde , alles, was Sie versuchten zu tun, war korrekt und sollte funktionieren, außer für den Teil, den Sie erwartet, einen Parameter zu helfen, herauszufinden, der Typ des anderen Parameters (wie Sie es geschrieben haben).

+1

+1. Nizza: "es verwendet nur Informationen von einer Parameterliste zur nächsten, aber nicht von einem Parameter zum nächsten" – pedrofurla

+0

danke für die klare Erklärung. Also ist Inferenz in der Tat unabhängig für Parameter innerhalb einer Liste ... aber, äh, warum ...? ;-) Ich sollte hinzufügen, dass das keine ganz ernst gemeinte Frage ist, da ich vermute, dass hier eine Antwort in These-Länge lauern könnte. –

+0

@Andrew Ich schätze Leistung. Es ist schwer genug, einen Typ zu bestimmen - was hier erforderlich wäre, wäre es, zwei Typen zu vereinheitlichen und dann ihre Schlussfolgerung zu finden. Das ist es, was Sprachen mit voller Typinferenz machen, aber nach Odersky ist es ein schweres Problem, es mit Sprachen mit einem Typsystem auf Augenhöhe mit Scala richtig zu machen. –

1

Eine andere Lösung ist quadratisch in teilweise angewandte Funktion zu heben:

scala> twice(square(_:Int),2) 
res1: Int = 16 

diese Weise wird die implizite angewendet wird, wie in Quadratur:

scala> twice(square(_:Int)(implicitly[Numeric[Int]]),2) 
res3: Int = 16 

Es gibt sogar ein anderer Ansatz:

def twice[T:Numeric](f: (T) => T, a: T): T = f(f(a)) 
scala> twice[Int](square,2) 
res1: Int = 16 

Aber wieder, der Typ Parameter nicht abgeleitet werden.

+0

Dies ist wahrscheinlich der bessere Ansatz. Aber was ich nicht mag, ist, dass ich explizit einen Typ angeben muss, bei dem es möglich ist, auf den Typ zu schließen. –

1

Ihr Problem ist, dass Quadrat ist keine Funktion (dh eine scala.Function1 [T, T] aka (T) => T). Stattdessen ist es ein Typ parametrisierte Methode mit mehreren Argumentlisten von denen eine implizit ist ... es gibt keine Syntax in Scala, um eine genau äquivalente Funktion zu definieren.

Interessanterweise Ihre Nutzung der numerischen Typ Klasse bedeutet, dass die üblichen Kodierungen von ranghöheren Funktionen in Scala Sie direkt hier nicht gelten, aber wir können sie auf diesen Fall anpassen und so etwas wie dies umgehen,

trait HigherRankedNumericFunction { 
    def apply[T : Numeric](t : T) : T 
} 

val square = new HigherRankedNumericFunction { 
    def apply[T : Numeric](t : T) : T = implicitly[Numeric[T]].times(t, t) 
} 

Dies gibt uns eine höherrangige „Funktion“ mit dem Typ-Parameter kontext begrenzt auf Numerisch,

scala> square(2) 
res0: Int = 4 

scala> square(2.0) 
res1: Double = 4.0 

scala> square("foo") 
<console>:8: error: could not find implicit value for evidence parameter of type Numeric[java.lang.String] 
    square("foo") 

Wir haben jetzt zweimal in Bezug auf HigherRankedNumericFunctions definieren können,

def twice[T : Numeric](f : HigherRankedNumericFunction, a : T) : T = f(f(a)) 

scala> twice(square, 2) 
res2: Int = 16 

scala> twice(square, 2.0) 
res3: Double = 16.0 

Der offensichtliche Nachteil dieses Ansatzes ist, dass Sie die Prägnanz von Scalas monomorphen Funktionsliteralen verlieren.

+0

Ok, ich habe es, aber ich bin noch nicht zufrieden. Ich möchte nicht zweimal nur für numerische Zeiten definiert werden. –

+0

Miles, wir könnten das weiterführen und ein 'Merkmal HigherKindedImplicitFunction [Kind [_]] {def apply [T: Kind] (t: T): T}' definieren, möglicherweise über viele weitere Parameter verallgemeinernd (wo Typen, die nicht 't braucht eine implizite könnte stattdessen eine 'Id [_]', oder? Dies würde dann nicht "zweimal" erfordern, eine Instanz von "Numerisch [T]" zu erfordern, sondern anstelle von "Kind [T]", was mit "Quadrat", "Numerisch" ist. – Adowrath

4

Hier ist eine Sitzung von der Scala REPL.

Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20). 
Type in expressions to have them evaluated. 
Type :help for more information. 

scala> def square[T : Numeric](n: T) = implicitly[Numeric[T]].times(n, n) 
square: [T](n: T)(implicit evidence$1: Numeric[T])T 

scala> def twice2[T](f: T => T)(a: T) = f(f(a)) 
twice2: [T](f: (T) => T)(a: T)T 

scala> twice2(square)(3) 
<console>:8: error: could not find implicit value for evidence parameter of type 
Numeric[T] 
     twice2(square)(3) 
      ^

scala> def twice3[T](a: T, f: T => T) = f(f(a)) 
twice3: [T](a: T,f: (T) => T)T 

scala> twice3(3, square) 
<console>:8: error: could not find implicit value for evidence parameter of type 
Numeric[T] 
     twice3(3, square) 

scala> def twice[T](a: T)(f: T => T) = f(f(a)) 
twice: [T](a: T)(f: (T) => T)T 

scala> twice(3)(square) 
res0: Int = 81 

So offenbar die Art der „zweimal (3)“ muss bekannt sein, bevor die implizite gelöst werden kann. Ich denke, das macht Sinn, aber ich wäre trotzdem froh, wenn ein Scala-Guru diesen hier kommentieren könnte ...

+0

@Scala Gurus: Wir gehen heute Abend mit einigen Clojure-Kollegen in eine "eleganteste Sprache" -Shooting und Scala muss natürlich gewinnen! Also alle guten Erklärungen sehr geschätzt ;-) –

+0

Siehe meine Antwort. Grundsätzlich erwartet man, dass der Typ abgeleitet wird, wo es nicht ist. –

+0

@Daniel liefert tatsächlich eine Erklärung für die ganze Sache. – pedrofurla

Verwandte Themen