Приложение частичной функции в 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? Я знаю, что в Haskell есть что-то, называемое «полным выводом», но я не знаю точно, что это значит ...

заранее спасибо


person Bruno Oliveira    schedule 22.08.2016    source источник
comment
Хорошая запись в блоге о каррировании и частичных функциях: vasinov.com/blog / on-currying-and-partial-function-application   -  person sebszyller    schedule 22.08.2016


Ответы (1)


Там разбросано несколько вопросов, поэтому я постараюсь ответить на них отдельно.

Что касается вывода типа, да, разделение списков параметров помогает компиляции определить тип f.

Это связано с тем, что Scala имеет линейный вывод локального типа (слева направо) и использует первый список параметров для вывода A (из типа l). Затем он может использовать эту информацию для определения типа f.

Учитывая, например,

dropWhile(List(1, 2, 3))(x => x < 3)

компилятор выполнит следующие шаги:

  • список первых параметров

    • A is unknown.
    • ожидается List[A]
    • предоставляется List[Int] (это определяется типом элементов в List)
    • => A is an Int
  • второй список параметров

    • we know A = Int
    • поэтому мы ожидаем функцию Int => Boolean как f

Если вы не разделите два списка параметров, компилятор не сможет «остановиться» и определить тип A перед проверкой типа f. f будет частью «разговора» при выборе типа A, поэтому вам нужно будет аннотировать его.

Это то, что Haskell может сделать лучше, поскольку он использует другую систему типов (Hindley- Милнер), который также может использовать информацию, полученную из контекста, в котором применяется функция. Вот почему его еще называют «полным» или «универсальным».

Почему в Scala нет системы типов Хиндли-Милнера? Короче говоря, потому что Scala также поддерживает подтипирование, которое вряд ли сосуществует с такой мощной системой типов. Подробнее по теме:

Что касается частичного применения, вопрос «почему это полезно» определенно слишком широк, чтобы на него можно было ответить здесь. Однако в конкретном 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))

но каррирование обеспечивает большую гибкость при составлении функций.

Подробнее по теме:

person Gabriele Petronella    schedule 22.08.2016
comment
Спасибо за отличный ответ !! - person Bruno Oliveira; 22.08.2016