A частичная функция - это любая функция, которая принимает только один аргумент, то есть определяется (то есть действует) только для определенного диапазона значений ее аргумента.Например, Math.asin
определено только для значений аргументов в диапазоне [-1.0, 1.0]
и не определено для значений за пределами этого диапазона - поэтому это частичная функция.Например, если мы вызываем Math.asin(5.0)
, мы получаем NaN
, что означает, что функция не определена для этого аргумента.
Обратите внимание, что частичная функция не обязательно должна вызывать исключение;ему просто нужно сделать что-то иное, чем вернуть действительное значение.
Ключевой принцип функционального программирования - ссылочная прозрачность ( RT ),Это означает, что мы должны иметь возможность заменить выражение (например, вызов функции) значением этого выражения, не меняя смысла программы.(Для получения дополнительной информации по этой теме я настоятельно рекомендую вам прочитать Функциональное программирование в Scala от Chiusano и Bjarnason.) Очевидно, что это не работает, если выброшено исключение или недопустимое значениевозвращаетсяЧтобы обращения к частичным функциям были прозрачными по ссылкам, мы можем вызывать их только со значениями аргументов, для которых они определены, или нам нужно элегантно обрабатывать неопределенные значения.Итак, как мы можем определить, определена ли частичная функция для некоторого произвольного значения аргумента?
В 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]
, поэтому тип second
s является экземпляром этого типа:
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
, более полезным, когда вы познакомитесь с функциональным программированием .Если вы не получите это прямо сейчас, не волнуйтесь;все станет ясно.