Применение частичной функции в Scala
Я изучаю функциональное программирование, следуя книге Пола Кьюзано и Рунара Бьярнасона " Функциональное программирование в Scala ". Я специально посвящен главе 3, где я реализую некоторые сопутствующие функции для класса, представляющего односвязный список, который предоставили авторы.
package fpinscala.datastructures
sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[+A](head: A, tail: List[A]) extends List[A]
object List {
def sum(ints: List[Int]): Int = ints match {
case Nil => 0
case Cons(x,xs) => x + sum(xs)
}
def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x,xs) => x * product(xs)
}
def apply[A](as: A*): List[A] =
if (as.isEmpty) Nil
else Cons(as.head, apply(as.tail: _*))
def tail[A](ls: List[A]): List[A] = ls match {
case Nil => Nil
case Cons(x,xs) => xs
}
... (more functions)
}
Функции, которые я реализую, входят в список объектов, являясь сопутствующими функциями.
При реализации dropWhile, чья сигнатура метода:
def dropWhile[A](l: List[A])(f: A => Boolean): List[A]
Я столкнулся с некоторыми вопросами относительно частичного применения функции:
В книге авторы говорят, что предикат f передается в отдельной группе аргументов, чтобы помочь компилятору scala с выводом типа, потому что, если мы сделаем это, Scala может определить тип f без каких-либо аннотаций, основываясь на том, что ему известно. о типе списка, что делает функцию более удобной в использовании.
Итак, если мы передадим f в одной группе аргументов, scala заставит вызов стать примерно таким: val total = List.dropWhile(example, (x:Int) => 6%x==0 )
где мы явно определяем тип x и "теряем" возможность частичного применения функции, я прав?
Однако почему в этом случае полезно применение частичных функций? Только чтобы сделать вывод типа? Имеет ли смысл "частично применять" такую функцию, как dropWhile, не применяя к ней предикат f? Потому что мне кажется, что вычисления становятся "остановленными", прежде чем они будут полезны, если мы не применим f...
Итак... почему полезно применение частичных функций? И так ли это всегда, или это что-то особенное для Scala? Я знаю, что у Хаскелла есть нечто, называемое "полным выводом", но я точно не знаю его последствий...
заранее спасибо
1 ответ
Там разбросано несколько вопросов, поэтому я постараюсь ответить на них отдельно.
Что касается вывода типа, да, разделение списков параметров помогает компиляции в выводе типа f
,
Это потому, что Scala имеет линейный вывод локального типа (слева направо) и использует первый список параметров для вывода A
(от типа l
). Затем он может использовать эту информацию для вывода типа f
,
Дано к примеру
dropWhile(List(1, 2, 3))(x => x < 3)
компилятор выполнит следующие шаги:
первый список параметров
A
неизвестно-
List[A]
ожидается -
List[Int]
предоставляется (это определяется типом элементов вList
) - =>
A
являетсяInt
второй список параметров
- мы знаем
A = Int
- поэтому мы ожидаем функцию
Int => Boolean
какf
- мы знаем
Если вы не разделите два списка параметров, компилятор не сможет "остановиться" и решить тип A
перед проверкой типа f
, f
будет частью "разговора" при определении типа A
так что вам нужно будет аннотировать его.
Это то, что Haskell может сделать лучше, так как он использует другую систему типов ( Hindley-Milner), которая также может использовать информацию, полученную из контекста, в котором применяется функция. Вот почему его также называют "полным" или "универсальным".
Почему в Scala отсутствует система типов Хиндли-Милнера? Короче говоря, потому что Scala также поддерживает подтип, который вряд ли сосуществует с такой мощной системой типов. Больше по теме:
- Почему вывод типа Scala не такой мощный, как у Haskell?
- http://www.codecommit.com/blog/scala/what-is-hindley-milner-and-why-is-it-cool
- http://www.scala-lang.org/old/node/4654
Что касается частичного применения, вопрос "почему это полезно", безусловно, слишком широк, чтобы ответить здесь. Однако в конкретном dropWhile
случай, предположим, у вас есть список функций, представляющих различные условия "отбрасывания". Используя частично примененную функцию, вы можете сделать:
val list = List(1, 2, 3)
val conditions: List[Int => Boolean] = List(_ < 1, _ < 2, _ < 3)
conditions.map(dropWhile(list)) // List(List(1, 2, 3), List(2, 3), List(3))
Очевидно, что с функцией без каррирования (то есть с одним списком параметров) вы могли бы достичь того же с
val list = List(1, 2, 3)
val conditions: List[Int => Boolean] = List(_ < 1, _ < 2, _ < 3)
conditions.map(cond => dropWhile(list, cond))
но карри обеспечивает большую гибкость при составлении функций.
Больше по теме: