> In the expression: print it
> In a 'do' expression: print it
GHCi сообщает нам, что выражение порождает функцию типа >a –> a
, но он не знает, как вывести её на экран. Функции не имеют экземпляра класса >Show
, так что мы не можем получить точное строковое представление функций. Когда мы вводим, скажем, >1 + 1
в терминале GHCi, он сначала вычисляет результат (>2
), а затем вызывает функцию >show
для >2
, чтобы получить текстовое представление этого числа. Текстовое представление >2
– это строка >"2"
, которая и выводится на экран.
ПРИМЕЧАНИЕ. Удостоверьтесь в том, что вы поняли, как работает каррирование и частичное применение функций, поскольку эти понятия очень важны.
Немного о высоких материях
Функции могут принимать функции в качестве параметров и возвращать функции в качестве значений. Чтобы проиллюстрировать это, мы собираемся создать функцию, которая принимает функцию, а затем дважды применяет её к чему-нибудь!
>applyTwice :: (a –> a) –> a –> a
>applyTwice f x = f (f x)
Прежде всего, обратите внимание на объявление типа. Раньше мы не нуждались в скобках, потому что символ >–>
обладает правой ассоциативностью. Однако здесь скобки обязательны. Они показывают, что первый параметр – это функция, которая принимает параметр некоторого типа и возвращает результат того же типа. Второй параметр имеет тот же тип, что и аргумент функции – как и возвращаемый результат. Мы можем прочитать данное объявление в каррированном стиле, но, чтобы избежать головной боли, просто скажем, что функция принимает два параметра и возвращает результат. Первый параметр – это функция (она имеет тип >a –> a
), второй параметр имеет тот же тип >a
. Заметьте, что совершенно неважно, какому типу будет соответствовать типовая переменная >a
– >Int
, >String
или вообще чему угодно – но при этом все значения должны быть одного типа.
ПРИМЕЧАНИЕ. Отныне мы будем говорить, что функция принимает несколько параметров, вопреки тому что в действительности каждая функция принимает только один параметр и возвращает частично применённую функцию. Для простоты будем говорить, что >a –> a –> a
принимает два параметра, хоть мы и знаем, что происходит «за кулисами».
Тело функции >applyTwice
достаточно простое. Мы используем параметр >f
как функцию, применяя её к параметру >x
(для этого разделяем их пробелом), после чего передаём результат снова в функцию >f
. Давайте поэкспериментируем с функцией:
>ghci> applyTwice (+3) 10
>16
>ghci> applyTwice (++ " ХА-ХА") "ЭЙ"
>"ЭЙ ХА-ХА ХА-ХА"
>ghci> applyTwice ("ХА-ХА " ++) "ЭЙ"
>"ХА-ХА ХА-ХА ЭЙ"
>ghci> applyTwice (multThree 2 2) 9
>144
>ghci> applyTwice (3:) [1]
>[3,3,1]
Красота и полезность частичного применения очевидны. Если наша функция требует передать ей функцию одного аргумента, мы можем частично применить функцию-параметр таким образом, чтобы оставался неопределённым всего один параметр, и затем передать её нашей функции. Например, функция >+
принимает два параметра; с помощью сечений мы можем частично применить её так, чтобы остался только один.
Реализация функции zipWith
Теперь попробуем применить ФВП для реализации очень полезной функции из стандартной библиотеки. Она называется