Как работает выражение `ap zip tail`

Я задавался вопросом, как написать f x = zip x (tail x) в точке бесплатно. Таким образом, я использовал программу pointfree, и результат был f = ap zip tail. ap является функцией из Control.Monad

Я не понимаю, как работает свободное определение точки. Надеюсь, я смогу понять это, если смогу понять это с точки зрения типов.

import Control.Monad (ap)
let f = ap zip tail
let g = ap zip
:info ap zip tail f g
ap :: Monad m => m (a -> b) -> m a -> m b
    -- Defined in `Control.Monad'
zip :: [a] -> [b] -> [(a, b)]   -- Defined in `GHC.List'
tail :: [a] -> [a]      -- Defined in `GHC.List'
f :: [b] -> [(b, b)]    -- Defined at <interactive>:3:5
g :: ([a] -> [b]) -> [a] -> [(a, b)]
    -- Defined at <interactive>:4:5

Посмотрев на выражение ap zip tail, я думаю, что zip является первым параметром ap, а tail - вторым параметром ap.

Monad m => m (a -> b) -> m a -> m b
           \--------/   \---/
              zip        tail

Но это невозможно, потому что типы zip и tail полностью отличаются от того, что требует функция ap. Даже с учетом того, что список является монадой.

Ответ 1

Таким образом, сигнатура типа ap равна Monad m => m (a -> b) -> m a -> m b. Вы дали ему zip и tail в качестве аргументов, поэтому давайте посмотрим на их сигнатуры типов.

Начиная с tail :: [a] -> [a] ~ (->) [a] [a] (здесь ~ - оператор равенства для типов), если мы сравниваем этот тип с типом второго аргумента для ap,

 (->) [x]  [x] ~ m a
((->) [x]) [x] ~ m a

получаем a ~ [x] и m ~ ((->) [x]) ~ ((->) a). Уже мы видим, что монада, в которой мы находимся, (->) [x], а не []. Если мы подставим то, что можем, в сигнатуру типа ap, получим:

(((->) [x]) ([x] -> b)) -> (((->) [x]) [x]) -> (((->) [x]) b)

Так как это не очень читаемо, его можно более обычно записывать как

  ([x] -> ([x] -> b)) -> ([x] -> [x]) -> ([x] -> b)
~ ([x] ->  [x] -> b ) -> ([x] -> [x]) -> ([x] -> b)

Тип zip - [x] -> [y] -> [(x, y)]. Мы уже можем видеть, что это соответствует первому аргументу ap, где

[x]         ~    [x]   
[y]         ~    [x]   
[(x, y)]    ~    b

Здесь я перечислил типы по вертикали, чтобы вы могли легко видеть, какие типы выстраиваются в линию. Очевидно, что x ~ x, y ~ x и [(x, y)] ~ [(x, x)] ~ b, поэтому мы можем закончить замену b ~ [(x, x)] на подпись типа ap и получить

([x] -> [x] -> [(x, x)]) -> ([x] -> [x]) -> ([x] -> [(x, x)])
--   zip                        tail        ( ap  zip  tail )
--                                            ap  zip  tail u = zip u (tail u)

Я надеюсь, что это очистит вас.

EDIT: danvari отметил в комментариях, монада (->) a иногда называется монадой читателя.

Ответ 2

Есть два аспекта для понимания этого:

  • Тип магии
  • Информационный поток реализации

Во-первых, это помогло мне понять магию типа:

1) zip          : [a] → ( [a] → [(a,a)] )
2) tail         : [a] → [a]
3) zip <*> tail : [a] → [(a,a)]

4) <*> : Applicative f ⇒ f (p → q) → f p → f q

В этом случае для <*>,

5) f x = y → x

Обратите внимание, что в 5, f является конструктором типа. Применение f to x создает тип. Кроме того, здесь = перегружается до средней эквивалентности типов.

y в настоящее время является держателем места, в этом случае это [a], что означает

6) f x = [a] -> x

Используя 6, мы можем переписать 1,2 и 3 следующим образом:

7) zip          : f ([a] → [(a,a)])
8) tail         : f [a]
9) zip <*> tail : f ([a] → [(a,a)])  →  f [a]  →  f [(a,a)]

Итак, глядя на 4, мы подставляем следующее:

10) p = [a]
11) q = [(a,a)]
12) f x =  [a] → x

(Повторение 6 здесь снова как 12)

Во-вторых, поток информации, то есть фактическая функциональность. Это проще, из определения <*> видно, что Аппликативный экземпляр y →, который переписывается здесь с разными именами идентификаторов и с использованием стиля infix:

13) g <*> h $ xs = g xs (h xs)

Подставляя следующее:

14) g = zip
15) h = tail

дает:

zip <*> tail $ xs        (Using 14 and 15)
  ==
zip xs (tail xs)         (Using 13 )