Объяснение частичной функции в книге Одерского

В книге Scala Odersky у него есть пример, объясняющий частичные функции страницы 295. Он начинается с этой функции:

val second: List[Int] => Int = {
    case x :: y :: _ => y
}

Таким образом, приведенная выше функция будет успешной, если вы передадите ей список из трех элементов, но не пустой список.

second(List(5,6,7))

работает, но не

second(List())

Вышеприведенное вызовет MatchError: List

Вот часть, которая меня смущает. Одерский пишет:

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

Зачем мне проверять, определена ли частичная функция. Что такое частичная функция? Это функция, которая применяется только к некоторым значениям?

Тип List[Int] => Int включает все функции из списков целых чисел в целые числа, независимо от того, являются ли функции частичными. Тип, который включает только частичные функции из списков целых чисел в целые числа, записывается как PartialFunction[List[Int], Int].

Итак, приведенная выше функция возвращает функцию типа List[Int] => Int, это я вижу, но зачем нам менять эту функцию на тип PartialFunction[List[Int], Int]?

Вот переопределенная функция:

val second: PartialFunction[List [Int], Int] = {
    case x :: y :: _ => y
}

Я действительно не понимаю. Какая польза? Почему мы хотим проверить, определена ли частичная функция? Что это хотя бы значит?


person Jwan622    schedule 17.05.2018    source источник


Ответы (2)


Частичная функция – это любая функция, принимающая только один аргумент, которая определена (т. е. действительна) только для определенного диапазона значений ее аргументов. Например, Math.asin определено только для значений аргументов в диапазоне [-1.0, 1.0] и не определено для значений за пределами этого диапазона, поэтому это частичная функция. Например, если мы вызываем Math.asin(5.0), мы возвращаем NaN, что означает, что функция не определена для этого аргумента.

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

Ключевым принципом функционального программирования является прозрачность ссылок (RT), означающая, что мы должны иметь возможность заменить выражение (например, вызов функции ) со значением этого выражения без изменения смысла программы. (Чтобы узнать больше по этой теме, я настоятельно рекомендую вам прочитать Функциональное программирование в Scala Кьюзано и Бьярнасона.) Очевидно, что это не работает, если возникает исключение или возвращается недопустимое значение. Чтобы вызовы частичных функций были ссылочно прозрачными, мы можем вызывать их только со значениями аргументов, для которых они определены, или нам нужно элегантно обрабатывать неопределенные значения. Итак, как мы можем узнать, определена ли частичная функция для некоторого произвольного значения аргумента?

В Scala частичные функции можно выразить как подкласс scala.PartialFunction, что позволяет нам ответить на этот вопрос.

Давайте посмотрим на ваш пример в сеансе Scala REPL...

$ scala
Welcome to Scala 2.12.6 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_171).
Type in expressions for evaluation. Or try :help.

scala> val second: List[Int] => Int = {
     |     case x :: y :: _ => y
     | }
<console>:11: warning: match may not be exhaustive.
It would fail on the following inputs: List(_), Nil
       val second: List[Int] => Int = {
                                  ^
second: List[Int] => Int = $$Lambda$3181/1894473818@27492c62

Так что мы только что сделали? Мы определили second как ссылку на функцию, которая принимает аргумент List[Int] и возвращает Int (второе значение в списке).

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

scala> second.isInstanceOf[PartialFunction[List[Int], Int]]
res0: Boolean = false

Между прочим, тип List[Int] => Int является сокращением для scala.Function1[List[Int], Int], поэтому тип seconds является экземпляром этого типа:

scala> second.isInstanceOf[Function1[List[Int], Int]]
res1: Boolean = true

Вызов этой версии функции дает указанные вами результаты:

scala> second(List(1, 2, 3))
res1: Int = 2

scala> second(Nil)
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
  at .$anonfun$second$1(<console>:11)
  at .$anonfun$second$1$adapted(<console>:11)
  ... 36 elided

Проблема в том, что если у нас просто есть какое-то значение списка, l, и мы не знаем, что находится в этом списке, мы не знаем, получим ли мы исключение, если передадим его функции, на которую ссылается second. Теперь мы могли бы поместить вызов в блок try и catch любое исключение, но это слишком многословно и не соответствует стилю функционального программирования. В идеале мы хотели бы знать, можем ли мы сначала вызвать функцию, чтобы избежать исключения. К сожалению, по экземпляру Function1 нельзя сказать:

scala> second.isDefinedAt(Nil)
<console>:13: error: value isDefinedAt is not a member of List[Int] => Int
       second.isDefinedAt(Nil)
              ^

Что нам нужно, так это объявить second, чтобы иметь тип PartialFunction[List[Int], Int] следующим образом:

scala> val second: PartialFunction[List[Int], Int] = {
     |   case x :: y :: _ => y
     | }
second: PartialFunction[List[Int],Int] = <function1>

(Кстати, обратите внимание, что у вас есть опечатка в вашем вопросе для этого кода - это должно быть определено выше.)

Теперь у нас нет никаких предупреждений! Мы сказали компилятору, что это экземпляр PartialFunction, поэтому компилятор знает, что он не определен для некоторых аргументов, поэтому предупреждения излишни. Теперь мы можем проверить этот факт:

scala> second.isInstanceOf[PartialFunction[List[Int], Int]]
res6: Boolean = true

Теперь мы также можем проверить, определен ли он для определенных значений:

scala> second.isDefinedAt(Nil)
res7: Boolean = false

scala> second.isDefinedAt(List(1, 2))
res9: Boolean = true

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

Итак, значит ли это, что теперь мы должны написать такой код:

def getSecondValue(l: List[Int]): Option[Int] = {

  // Check if second is defined for this argument. If so, call it and wrap in Some.
  if(second.isDefinedAt(l)) Some(second(l))

  // Otherwise, we do not have a second value.
  else None
}

Ну, это тоже немного многословно. К счастью, когда second является экземпляром PartialFunction, мы можем переписать приведенное выше как:

def getSecondValue(l: List[Int]): Option[Int] = second.lift(l)

Метод lift превращает частичную функцию в полную функцию, которая возвращает определенное значение для каждого аргумента: если аргумент second определен, мы получаем Some(value); иначе получаем None.

Вы обнаружите, что концепция частичных функций и PartialFunction становится более полезной по мере того, как вы лучше знакомитесь с функциональным программированием. Если вы не понимаете прямо сейчас, не волнуйтесь; все станет ясно.

person Mike Allen    schedule 17.05.2018

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

val divide = (x: Int) => 42 / x

Как определено, эта функция срабатывает, когда входной параметр равен нулю:

scala> divide(0)
java.lang.ArithmeticException: / by zero

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

val divide = new PartialFunction[Int, Int] {
def apply(x: Int) = 42 / x
def isDefinedAt(x: Int) = x != 0
}

https://alvinalexander.com/scala/how-to-define-use-partial-functions-in-scala-syntax-examples

Вы можете обратиться по ссылке выше.

person Raman Mishra    schedule 17.05.2018