Использование частичных функций в Scala - как это работает? - PullRequest
47 голосов
/ 28 декабря 2011

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

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

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

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

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

Ответы [ 2 ]

142 голосов
/ 28 декабря 2011

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

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
35 голосов
/ 28 декабря 2011

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

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

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

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

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

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

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

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

...