Использование частичных функций в 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
, заметьте. Я просто говорю, что частичные функции - это делать что-то еще, когда для него не определен вход.