Объявление типа не изменилось, так как выражение compare 100
возвращает функцию. Функция compare
имеет тип (Ord a) => a –> (a –> Ordering)
. Когда мы применим её к 100, то получим функцию, принимающую целое число и возвращающую значение типа Ordering
.
Сечения
Инфиксные функции могут быть частично применены при помощи так называемых
divideByTen :: (Floating a) => a –> a
divideByTen = (/10)
Вызов, скажем, divideByTen 200
эквивалентен вызову 200 / 10
, равно как и (/10) 200
:
ghci> divideByTen 200
20.0
ghci> 200 / 10
20.0
ghci> (/10) 200
20.0
А вот функция, которая проверяет, находится ли переданный символ в верхнем регистре:
isUpperAlphanum :: Char –> Bool
isUpperAlphanum = (`elem` ['А'..'Я'])
Единственная особенность при использовании сечений – применение знака «минус». По определению сечений, (–4)
– это функция, которая вычитает четыре из переданного числа. В то же время для удобства (–4) означает «минус четыре». Если вы хотите создать функцию, которая вычитает четыре из своего аргумента, выполняйте частичное применение таким образом: (subtract 4)
.
Печать функций
До сих пор мы давали частично применённым функциям имена, после чего добавляли недостающие параметры, чтобы всё-таки посмотреть на результаты. Однако мы ни разу не попробовали напечатать сами функции. Попробуем? Что произойдёт, если мы попробуем выполнить multThree 3 4
в GHCi вместо привязки к имени с помощью ключевого слова let
либо передачи другой функции?
ghci> multThree 3 4
No instance for (Show (a –> a))
arising from a use of `print' at
Possible fix: add an instance declaration for (Show (a –> a))
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
или вообще чему угодно – но при этом все значения должны быть одного типа.