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


81

У 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

Я знаю лише одну різницю - це частково застосовані функції. Але обидва шляхи для мене рівнозначні.

Чи є інші відмінності?

Відповіді:


88

Власне кажучи, це не функція, що працює, а метод із декількома списками аргументів, хоча, правда, це виглядає як функція.

Як ви вже сказали, декілька списків аргументів дозволяють використовувати метод замість частково застосованої функції. (Вибачте за загально дурні приклади, які я використовую)

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

Ще одна перевага полягає в тому, що ви можете використовувати фігурні дужки замість дужок, що виглядає добре, якщо другий список аргументів складається з однієї функції або функції. Напр

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
}

Або для думки:

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
Оскільки це найбільш відповідна відповідь, я думаю, що назва запитання більше не відповідає його відповіді. Я думаю, що заголовок слід змінити на, скажімо, "Чому Scala надає як кілька списків параметрів, так і кілька параметрів у списку?", Тобто він вже об'єднаний зразками зі stackoverflow.com/questions/4684185/… .
Яцек Ласковський

42

Є ще одна різниця, на яку не поширилася чудова відповідь 0 __ : параметри за замовчуванням. Параметр з одного списку параметрів можна використовувати під час обчислення за замовчуванням в іншому списку параметрів, але не в тому самому.

Наприклад:

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

Взяв цей простий приклад, щоб він занурився. Це насправді робить параметри за замовчуванням набагато кориснішими. Дякую!
Mike McFarland

Хороший приклад, за винятком того, що я витратив п'ять хвилин, щоб зрозуміти, як це назвати: g(1)()повертається 3. g(1)(2)повертається 5.
Sapience

19

У цьому вся суть, полягає в тому, що карировані та необроблені форми рівноцінні! Як зазначали інші, та чи інша форма може бути синтаксично зручнішою для роботи в залежності від ситуації, і це єдина причина віддавати перевагу одній над іншою.

Важливо розуміти, що навіть якщо Scala не мала спеціального синтаксису для оголошення функцій, що функціонують, ви все одно можете їх побудувати; це лише математична неминучість, коли у вас є можливість створювати функції, які повертають функції.

Щоб продемонструвати це, уявіть, що def foo(a)(b)(c) = {...}синтаксис не існував. Тоді ще можна було досягти точно таку ж річ , як так: def foo(a) = (b) => (c) => {...}.

Як і багато інших функцій у Scala, це лише синтаксична зручність для виконання чогось, що було б можливо в будь-якому випадку, але з трохи більшою деталізацією.


4

Ці дві форми ізоморфні. Основна відмінність полягає в тому, що функцію, що функціонує, легше застосувати частково, тоді як функція, що не працює, має дещо приємніший синтаксис, принаймні в Scala.


2
Хіба раніше не було сказано, що приклади - це не функціональні функції? Я розумію, що функція curried має лише один аргумент і може повертати функцію з одним аргументом і так далі, поки не буде тіла з усіма аргументами, які закриті. Я помиляюся?
Яцек Ласковський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.