Какая разница между списками параметров и несколькими параметрами в списке в Scala?

В Scala можно написать функции (curried?), подобные этой

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

В чем разница между приведенным выше определением функции curriedFunc с двумя списками параметров и функциями с несколькими параметрами в одном списке параметров:

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

С математической точки зрения это (curriedFunc(x))(y) и curriedFunc(x,y), но я могу написать def sum(x) (y) = x + y, и то же самое будет def sum2(x, y) = x + y

Я знаю только одно отличие - это частично применяемые функции. Но оба варианта для меня эквивалентны.

Есть ли другие отличия?

Ответы

Ответ 1

Строго говоря, это не валютная функция, а метод с несколькими списками аргументов, хотя, по общему признанию, он выглядит как функция.

Как вы сказали, списки множественных аргументов позволяют использовать этот метод вместо частично применяемой функции. (Извините за обычные глупые примеры, которые я использую)

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

Другим преимуществом является то, что вы можете использовать фигурные скобки вместо круглых скобок, которые выглядят хорошо, если второй список аргументов состоит из одной функции или thunk. Например.

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

против

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

Или для thunk:

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

Еще одно преимущество: вы можете ссылаться на аргументы предыдущего списка аргументов для определения значений аргументов по умолчанию (хотя вы также можете сказать, что это недостаток, который вы не можете сделать в одном списке:)

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

Наконец, есть еще три приложения в ответ на связанный пост Почему Scala предоставляет как список нескольких параметров, так и несколько параметров для списка?. Я просто скопирую их здесь, но это заслуга Кнута Арне Веды, Кевина Райт и extempore.

Во-первых: вы можете иметь несколько аргументов var:

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

... что невозможно в одном списке аргументов.

Во-вторых, это помогает сделать вывод типа:

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

И последнее, это единственный способ, которым вы можете иметь неявные и неявные аргументы, поскольку implicit является модификатором для всего списка аргументов:

def gaga [A](x: A)(implicit mf: Manifest[A]) = ???   // ok
def gaga2[A](x: A, implicit mf: Manifest[A]) = ???   // not possible

Ответ 2

Там другая разница, которая не была покрыта 0__ отличным answer: параметры по умолчанию. Параметр из одного списка параметров может использоваться при вычислении значения по умолчанию в другом списке параметров, но не в том же самом.

Например:

def f(x: Int, y: Int = x * 2) = x + y // not valid
def g(x: Int)(y: Int = x * 2) = x + y // valid

Ответ 3

Все дело в том, что карри и неокрашенные формы эквивалентны! Как указывали другие, одна или другая форма может быть синтаксически более удобной для работы в зависимости от ситуации, и это единственная причина, чтобы предпочесть ее друг другу.

Важно понимать, что даже если Scala не имел специального синтаксиса для объявления curried-функций, вы все равно могли бы их построить; это просто математическая неизбежность, когда у вас есть возможность создавать функции, возвращающие функции.

Чтобы продемонстрировать это, представьте, что синтаксис def foo(a)(b)(c) = {...} не существует. Тогда вы все равно можете достичь такой же вещи: def foo(a) = (b) => (c) => {...}.

Как и многие функции в Scala, это просто синтаксическое удобство для выполнения чего-то, что было бы возможно в любом случае, но с немного большей детализацией.

Ответ 4

Две формы изоморфны. Основное отличие состоит в том, что карри-функции легче применять частично, а функции, отличные от валюты, имеют немного более сильный синтаксис, по крайней мере, в Scala.