Was ist der Unterschied zwischen mehreren Parameterlisten und mehreren Parametern pro Liste in Scala?

81

In Scala kann man solche (Curry-?) Funktionen schreiben

def curriedFunc(arg1: Int) (arg2: String) = { ... }

Was ist der Unterschied zwischen der obigen curriedFuncFunktionsdefinition mit zwei Parameterlisten und Funktionen mit mehreren Parametern in einer einzigen Parameterliste:

def curriedFunc(arg1: Int, arg2: String) = { ... }

Aus mathematischer Sicht ist dies (curriedFunc(x))(y)und curriedFunc(x,y)ich kann schreiben def sum(x) (y) = x + yund das gleiche wird es seindef sum2(x, y) = x + y

Ich kenne nur einen Unterschied - dies sind teilweise angewandte Funktionen. Aber beide Wege sind für mich gleichwertig.

Gibt es noch andere Unterschiede?

den bardadym
quelle

Antworten:

88

Genau genommen ist dies keine Curry-Funktion, sondern eine Methode mit mehreren Argumentlisten, obwohl sie zugegebenermaßen wie eine Funktion aussieht.

Wie Sie sagten, ermöglichen die Listen mit mehreren Argumenten, dass die Methode anstelle einer teilweise angewendeten Funktion verwendet wird. (Entschuldigung für die allgemein albernen Beispiele, die ich benutze)

object NonCurr {
  def tabulate[A](n: Int, fun: Int => A) = IndexedSeq.tabulate(n)(fun)
}

NonCurr.tabulate[Double](10, _)            // not possible
val x = IndexedSeq.tabulate[Double](10) _  // possible. x is Function1 now
x(math.exp(_))                             // complete the application

Ein weiterer Vorteil ist, dass Sie geschweifte Klammern anstelle von Klammern verwenden können, was gut aussieht, wenn die zweite Argumentliste aus einer einzelnen Funktion oder einem Thunk besteht. Z.B

NonCurr.tabulate(10, { i => val j = util.Random.nextInt(i + 1); i - i % 2 })

gegen

IndexedSeq.tabulate(10) { i =>
  val j = util.Random.nextInt(i + 1)
  i - i % 2
}

Oder für den Thunk:

IndexedSeq.fill(10) {
  println("debug: operating the random number generator")
  util.Random.nextInt(99)
}

Ein weiterer Vorteil ist, dass Sie zum Definieren von Standardargumentwerten auf Argumente einer vorherigen Argumentliste verweisen können (obwohl Sie auch sagen könnten, dass es ein Nachteil ist, dass Sie dies nicht in einer einzelnen Liste tun können :)

// again I'm not very creative with the example, so forgive me
def doSomething(f: java.io.File)(modDate: Long = f.lastModified) = ???

Schließlich gibt es drei weitere Anwendungen in einer Antwort auf einen verwandten Beitrag. Warum bietet Scala sowohl mehrere Parameterlisten als auch mehrere Parameter pro Liste an? . Ich werde sie hier nur kopieren, aber der Kredit geht an Knut Arne Vedaa, Kevin Wright und extempore.

Erstens: Sie können mehrere var args haben:

def foo(as: Int*)(bs: Int*)(cs: Int*) = as.sum * bs.sum * cs.sum

... was in einer einzigen Argumentliste nicht möglich wäre.

Zweitens hilft es bei der Typinferenz:

def foo[T](a: T, b: T)(op: (T,T) => T) = op(a, b)
foo(1, 2){_ + _}   // compiler can infer the type of the op function

def foo2[T](a: T, b: T, op: (T,T) => T) = op(a, b)
foo2(1, 2, _ + _)  // compiler too stupid, unfortunately

Und schließlich ist dies die einzige Möglichkeit, implizite und nicht implizite implicitArgumente zu haben , ebenso wie ein Modifikator für eine ganze Argumentliste:

def gaga [A](x: A)(implicit mf: Manifest[A]) = ???   // ok
def gaga2[A](x: A, implicit mf: Manifest[A]) = ???   // not possible
0__
quelle
2
Da es die am häufigsten gewählte Antwort ist, entspricht der Titel der Frage meiner Meinung nach nicht mehr der Antwort. Ich denke, der Titel sollte beispielsweise in "Warum bietet Scala sowohl mehrere Parameterlisten als auch mehrere Parameter pro Liste?" Geändert werden , dh er wird bereits von den Beispielen mit mit stackoverflow.com/questions/4684185/… zusammengeführt .
Jacek Laskowski
42

Es gibt noch einen weiteren Unterschied, der von der hervorragenden Antwort von 0 __ nicht abgedeckt wurde : Standardparameter. Ein Parameter aus einer Parameterliste kann verwendet werden, wenn der Standard in einer anderen Parameterliste berechnet wird, jedoch nicht in derselben.

Zum Beispiel:

def f(x: Int, y: Int = x * 2) = x + y // not valid
def g(x: Int)(y: Int = x * 2) = x + y // valid
Daniel C. Sobral
quelle
Ich habe dieses einfache Beispiel genommen, um dies zu berücksichtigen. Das macht Standardparameter viel nützlicher. Vielen Dank!
Mike McFarland
Gutes Beispiel, außer dass ich fünf Minuten damit verbracht habe, herauszufinden, wie man es nennt: g(1)()gibt 3. g(1)(2)zurück 5. gibt 5. zurück
Sapience
19

Das ist der springende Punkt: Die Curry- und die Curry-Form sind gleichwertig! Wie andere bereits betont haben, kann es je nach Situation syntaktisch bequemer sein, mit der einen oder anderen Form zu arbeiten, und dies ist der einzige Grund, die eine der anderen vorzuziehen.

Es ist wichtig zu verstehen, dass Sie Scala auch dann erstellen können, wenn Sie keine spezielle Syntax zum Deklarieren von Curry-Funktionen haben. Dies ist nur eine mathematische Unvermeidlichkeit, wenn Sie Funktionen erstellen können, die Funktionen zurückgeben.

Um dies zu demonstrieren, stellen Sie sich vor, dass die def foo(a)(b)(c) = {...}Syntax nicht vorhanden war. Dann könnten Sie immer noch genau das Gleiche erreichen : def foo(a) = (b) => (c) => {...}.

Wie viele Funktionen in Scala ist dies nur eine syntaktische Annehmlichkeit, um etwas zu tun, das sowieso möglich wäre, aber mit etwas mehr Ausführlichkeit.

Tom Crockett
quelle
4

Die beiden Formen sind isomorph. Der Hauptunterschied besteht darin, dass Curry-Funktionen teilweise einfacher anzuwenden sind, während Nicht-Curry-Funktionen zumindest in Scala eine etwas schönere Syntax haben.

Hammar
quelle
2
Wurde nicht früher gesagt, dass die Beispiele keine Curry-Funktionen sind? Ich verstehe, dass eine Curry-Funktion nur ein einziges Argument hat und möglicherweise eine Funktion mit einem einzigen Argument usw. zurückgibt, bis ein Textkörper mit allen Argumenten geschlossen ist. Liege ich falsch?
Jacek Laskowski