Scala Language
Частичные функции
Поиск…
Состав
Частичные функции часто используются для определения полной функции по частям:
sealed trait SuperType
case object A extends SuperType
case object B extends SuperType
case object C extends SuperType
val pfA: PartialFunction[SuperType, Int] = {
case A => 5
}
val pfB: PartialFunction[SuperType, Int] = {
case B => 10
}
val input: Seq[SuperType] = Seq(A, B, C)
input.map(pfA orElse pfB orElse {
case _ => 15
}) // Seq(5, 10, 15)
При этом использовании частичные функции предпринимаются в порядке конкатенации с orElse
метода orElse
. Как правило, предоставляется заключительная частичная функция, которая соответствует всем остальным случаям. В совокупности комбинация этих функций действует как общая функция.
Этот шаблон обычно используется для разделения проблем, когда функция может эффективно выполнять диспетчер для разрозненных путей кода. Это обычное явление, например, в методе приема Акка Акка .
Использование с `collect`
Хотя частичная функция часто используется в качестве удобного синтаксиса для общих функций, путем включения окончательного подстановочного соответствия ( case _
) в некоторых методах их пристрастность является ключевым. Один очень распространенный пример в идиоматической Scala - это метод collect
, определенный в библиотеке коллекций Scala. Здесь частичные функции позволяют общими функциями проверки элементов коллекции отображать и / или фильтровать их в одном компактном синтаксисе.
Пример 1
Предполагая, что мы имеем функцию квадратного корня, определенную как частичная функция:
val sqRoot:PartialFunction[Double,Double] = { case n if n > 0 => math.sqrt(n) }
Мы можем вызвать его с помощью комбинатора collect
:
List(-1.1,2.2,3.3,0).collect(sqRoot)
эффективно выполняя ту же операцию, что и:
List(-1.1,2.2,3.3,0).filter(sqRoot.isDefinedAt).map(sqRoot)
Пример 2.
sealed trait SuperType // `sealed` modifier allows inheritance within current build-unit only
case class A(value: Int) extends SuperType
case class B(text: String) extends SuperType
case object C extends SuperType
val input: Seq[SuperType] = Seq(A(5), B("hello"), C, A(25), B(""))
input.collect {
case A(value) if value < 10 => value.toString
case B(text) if text.nonEmpty => text
} // Seq("5", "hello")
В приведенном выше примере есть несколько вещей:
- Левая часть каждого совпадения шаблонов эффективно выбирает элементы для обработки и включения в выход. Любое значение, которое не имеет подходящего
case
, просто опускается. - Правая часть определяет применимую к делу обработку.
- Соответствие шаблону связывает переменную для использования в защитных выражениях (предложения
if
) и в правой части.
Основной синтаксис
Scala имеет специальный тип функции, называемый частичной функцией , которая расширяет нормальные функции, что означает, что экземпляр PartialFunction
можно использовать везде, где ожидается Function1
. Частичные функции могут быть определены анонимно с использованием синтаксиса case
также используемого при сопоставлении шаблонов :
val pf: PartialFunction[Boolean, Int] = {
case true => 7
}
pf.isDefinedAt(true) // returns true
pf(true) // returns 7
pf.isDefinedAt(false) // returns false
pf(false) // throws scala.MatchError: false (of class java.lang.Boolean)
Как видно из примера, частичная функция не обязательно должна быть определена во всей области ее первого параметра. Стандартный экземпляр Function1
считается полным , что означает, что он определен для каждого возможного аргумента.
Использование в качестве общей функции
Частичные функции очень распространены в идиоматической Scala. Они часто используются для их удобного синтаксиса на основе case
для определения общих функций по признакам :
sealed trait SuperType // `sealed` modifier allows inheritance within current build-unit only
case object A extends SuperType
case object B extends SuperType
case object C extends SuperType
val input: Seq[SuperType] = Seq(A, B, C)
input.map {
case A => 5
case _ => 10
} // Seq(5, 10, 10)
Это сохраняет дополнительный синтаксис оператора match
в обычной анонимной функции. Для сравнения:
input.map { item =>
item match {
case A => 5
case _ => 10
}
} // Seq(5, 10, 10)
Он также часто используется для выполнения декомпозиции параметров с использованием сопоставления шаблонов, когда кортеж или класс case передаются функции:
val input = Seq("A" -> 1, "B" -> 2, "C" -> 3)
input.map { case (a, i) =>
a + i.toString
} // Seq("A1", "B2", "C3")
Использование для извлечения кортежей в функции карты
Эти три функции карты эквивалентны, поэтому используйте вариацию, которую ваша команда считает наиболее читаемой.
val numberNames = Map(1 -> "One", 2 -> "Two", 3 -> "Three")
// 1. No extraction
numberNames.map(it => s"${it._1} is written ${it._2}" )
// 2. Extraction within a normal function
numberNames.map(it => {
val (number, name) = it
s"$number is written $name"
})
// 3. Extraction via a partial function (note the brackets in the parentheses)
numberNames.map({ case (number, name) => s"$number is written $name" })
Частичная функция должна соответствовать всем входам : любой случай, который не соответствует, генерирует исключение во время выполнения.