>zipWith' :: (a –> b –> c) –> [a] –> [b] –> [c]
>zipWith' _ [] _ = []
>zipWith' _ _ [] = []
>zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ys
Посмотрите на объявление типа. Первый параметр – это функция, которая принимает два значения и возвращает одно. Параметры этой функции не обязательно должны быть одинакового типа, но могут. Второй и третий параметры – списки. Результат тоже является списком. Первым идёт список элементов типа >a
, потому что функция сцепления принимает значение типа >a
в качестве первого параметра. Второй должен быть списком из элементов типа >b
, потому что второй параметр у связывающей функции имеет тип >b
. Результат – список элементов типа >c
. Если объявление функции говорит, что она принимает функцию типа >a –> b –> c
как параметр, это означает, что она также примет и функцию >a –> a –> a
, но не наоборот.
ПРИМЕЧАНИЕ. Запомните: когда вы создаёте функции, особенно высших порядков, и не уверены, каким должен быть тип, вы можете попробовать опустить объявление типа, а затем проверить, какой тип выведет язык Haskell, используя команду >:t
в GHCi.
Устройство данной функции очень похоже на обычную функцию >zip
. Базовые случаи одинаковы. Единственный дополнительный аргумент – соединяющая функция, но он не влияет на базовые случаи; мы просто используем для него маску подстановки >_
. Тело функции в последнем образце также очень похоже на функцию >zip
– разница в том, что она не создаёт пару >(x, y)
, а возвращает >f x y
. Одна функция высшего порядка может использоваться для решения множества задач, если она достаточно общая. Покажем на небольшом примере, что умеет наша функция >zipWith'
:
>ghci> zipWith' (+) [4,2,5,6] [2,6,2,3]
>[6,8,7,9]
>ghci> zipWith' max [6,3,2,1] [7,3,1,5]
>[7,3,2,5]
>ghci> zipWith' (++) ["шелдон ", "леонард "] ["купер", "хофстадтер"]
>["шелдон купер","леонард хофстадтер"]
>ghci> zipWith' (*) (replicate 5 2) [1..]
>[2,4,6,8,10]
>ghci> zipWith' (zipWith' (*)) [[1,2,3],[3,5,6],[2,3,4]] [[3,2,2],[3,4,5],[5,4,3]] [[3,4,6],[9,20,30],[10,12,12]]
Как видите, одна-единственная функция высшего порядка может применяться самыми разными способами.
Реализация функции flip
Теперь реализуем ещё одну функцию из стандартной библиотеки, >flip
. Функция >flip
принимает функцию и возвращает функцию. Единственное отличие результирующей функции от исходной – первые два параметра переставлены местами. Мы можем реализовать >flip
следующим образом:
>flip' :: (a –> b –> c) –> (b –> a –> c)
>flip' f = g
> where g x y = f y x
Читая декларацию типа, мы видим, что функция принимает на вход функцию с параметрами типов >a
и >b
и возвращает функцию с параметрами >b
и >a
. Так как все функции на самом деле каррированы, вторая пара скобок не нужна, поскольку символ >–>
правоассоциативен. Тип >(a –> b –> c) –> (b –> a –> c)
– то же самое, что и тип >(a –> b –> c) –> (b –> (a –> c))
, а он, в свою очередь, представляет то же самое, что и тип >(a –> b –> c) –> b –> a –> c