Ленивость Haskell позволит вычислить только то, что действительно необходимо. Функция &&
устроена таким образом, что если её первый параметр False
, то второй просто игнорируется, поскольку и так ясно, что результат должен быть False
.
Функция foldr
будет работать с бесконечными списками, если бинарная функция, которую мы ей передаём, не требует обязательного вычисления второго параметра, если значения первого ей достаточно для вычисления результата. Такова функция &&
– ей неважно, каков второй параметр, при условии, что первый — False
.
Сканирование
Функции scanl
и scanr
похожи на foldl
и foldr
, только они сохраняют все промежуточные значения аккумулятора в список. Также существуют функции scanl1
и scanr1
, которые являются аналогами foldl1
и foldr1
.
ghci> scanl (+) 0 [3,5,2,1]
[0,3,8,10,11]
ghci> scanr (+) 0 [3,5,2,1]
[11,8,3,1,0]
ghci> scanl1 (\acc x –> if x > acc then x else acc) [3,4,5,3,7,9,2,1]
[3,4,5,5,7,9,9,9]
ghci> scanl (flip (:)) [] [3,2,1]
[[],[3],[2,3],[1,2,3]]
При использовании функции scanl
финальный результат окажется в последнем элементе итогового списка, тогда как функция scanr
поместит результат в первый элемент.
Функции сканирования используются для того, чтобы увидеть, как работают функции, которые можно реализовать как свёртки. Давайте ответим на вопрос: как много корней натуральных чисел нам потребуется, чтобы их сумма превысила 1000? Чтобы получить сумму квадратов натуральных чисел, воспользуемся map sqrt [1..]
. Теперь, чтобы получить сумму, прибегнем к помощи свёртки, но поскольку нам интересно знать, как увеличивается сумма, будем вызывать функцию scanl1
. После вызова scanl1
посмотрим, сколько элементов не превышают 1000. Первый элемент в результате работы функции scanl1
должен быть равен единице. Второй будет равен 1 плюс квадратный корень двух. Третий элемент – это корень трёх плюс второй элемент. Если у нас
sqrtSums :: Int
sqrtSums = length (takeWhile (< 1000) (scanl1 (+) (map sqrt [1..]))) + 1
ghci> sqrtSums
131
ghci> sum (map sqrt [1..131])
1005.0942035344083
ghci> sum (map sqrt [1..130])
993.6486803921487
Мы задействовали функцию takeWhile
вместо filter
, потому что последняя не работает на бесконечных списках. В отличие от нас, функция filter
не знает, что список возрастает, поэтому мы используем takeWhile
, чтобы отсечь список, как только сумма превысит 1000.
Применение функций с помощью оператора $
Пойдём дальше. Теперь объектом нашего внимания станет оператор $
, также называемый
($) :: (a –> b) –> a –> b
f $ x = f x
Зачем? Что это за бессмысленный оператор? Это просто применение функции! Верно, $
имеет самый низкий приоритет. Применение функции с пробелом левоассоциативно (то есть f a b c i
– это то же самое, что (((f a) b) c))
, в то время как применение функции при помощи оператора $
правоассоциативно.
Всё это прекрасно, но нам-то с того какая польза? Прежде всего оператор $
удобен тем, что с ним не приходится записывать много вложенных скобок. Рассмотрим выражение sum (map sqrt [1..130])
. Поскольку оператор $
имеет самый низкий приоритет, мы можем переписать это выражение как sum $ map sqrt [1..130]
, сэкономив драгоценные нажатия на клавиши. Когда в функции встречается знак $
, выражение справа от него используется как параметр для функции слева от него. Как насчёт sqrt 3 + 4 + 9
? Здесь складываются 9
, 4
и корень из 3
. Если мы хотим получить квадратный корень суммы, нам надо написать sqrt (3 + 4 + 9)
– или же (в случае использования оператора $
) sqrt $ 3 + 4 + 9
, потому что у оператора $
низший приоритет среди всех операторов. Вот почему вы можете представить символ $
как эквивалент записи открывающей скобки с добавлением закрывающей скобки в крайней правой позиции выражения.
Посмотрим ещё на один пример:
ghci> sum (filter (> 10) (map (*2) [2..10]))
80
Очень много скобок, даже как-то уродливо. Поскольку оператор $ правоассоциативен, выражение f (g (z x))
эквивалентно записи f $ g $ z x
. Поэтому пример можно переписать: