Применение частичной функции в 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 также поддерживает подтип, который вряд ли сосуществует с такой мощной системой типов. Больше по теме:

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

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

Больше по теме:

Другие вопросы по тегам