Использование частичных функций в Scala - как это работает?

Я новичок в Scala, я использую 2.9.1, и я пытаюсь понять, как использовать частичные функции. У меня есть базовое представление о функциях в карри, и я знаю, что частичные функции похожи на кардианные функции, где они только 2nary или некоторые из них. Как вы можете сказать, я немного зелёный.

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

У меня есть функция, которая использует структуру RewriteRule, но мне нужно, чтобы она работала с двумя аргументами, тогда как структура RewriteRule принимает только одну, ИЛИ частичную функцию. Я думаю, что это один из тех случаев, когда я думаю о том, что это полезно.

Любые советы, ссылки, слова мудрости и т.д. приветствуются!

Ответы до сих пор превосходны и прояснили некоторые фундаментальные заблуждения, которые у меня есть. Я думаю, что они также объясняют, с чем я борюсь - я думаю, что, возможно, будет задан новый вопрос, который будет более конкретным, поэтому я тоже это сделаю.

Ответ 1

Частичная функция - это функция, действительная только для подмножества значений тех типов, которые вы могли бы передать ей. Например:

val root: PartialFunction[Double,Double] = {
  case d if (d >= 0) => math.sqrt(d)
}

scala> root.isDefinedAt(-1)
res0: Boolean = false

scala> root(3)
res1: Double = 1.7320508075688772

Это полезно, если у вас есть что-то, что знает, как проверить, определена ли функция. Соберите, например:

scala> List(0.5, -0.2, 4).collect(root)   // List of _only roots which are defined_
res2: List[Double] = List(0.7071067811865476, 2.0)

Это не поможет вам разместить два аргумента, где вы действительно этого хотите.

Напротив, частично примененная функция - это функция, в которой некоторые из ее аргументов уже заполнены.

def add(i: Int, j: Int) = i + j
val add5 = add(_: Int,5)

Теперь вам нужен только один аргумент - вещь для добавления 5 в - вместо двух:

scala> add5(2)
res3: Int = 7

В этом примере вы можете увидеть, как его использовать.

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

val addTupled = (add _).tupled

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

scala> List((1,2), (4,5), (3,8)).map(addTupled)
res4: List[Int] = List(3, 9, 11)

В отличие от этого, каррирование еще раз; он превращает функции вида (A,B) => C в A => B => C. То есть, если задана функция из нескольких аргументов, она создаст цепочку функций, каждая из которых примет один аргумент и вернет цепочку еще короче (вы можете думать об этом как о частичном применении одного аргумента за раз).

val addCurried = (add _).curried

scala> List(1,4,3).map(addCurried)
res5: List[Int => Int] = List(<function1>, <function1>, <function1>)

scala> res5.head(2)   // is the first function, should add 1
res6: Int = 3

scala> res5.tail.head(5)   // Second function should add 4
res7: Int = 9

scala> res5.last(8)  // Third function should add 3
res8: Int = 11

Ответ 2

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

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

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

Другие упоминают метод isDefinedAt, который, действительно, является разницей, но в основном касается реализации. То, что Scala 2.10, вероятно, будет выпущено с "быстрой частичной функцией", которая не полагается на isDefinedAt.

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

Какие частичные функции действительно сходят на нет, это другой метод: orElse. Это суммирует все варианты использования для частичных функций намного лучше, чем isDefinedAt, потому что частичные функции действительно выполняют одно из следующих действий:

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

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