Почему присоединяется.(перевернуть карту) имеют тип ((A -> B) -> A) -> (A -> B) -> B? - PullRequest
0 голосов
/ 31 мая 2018

Некоторые игры с функторами и монадами в ghci привели меня к значению, тип и поведение которого я хотел бы лучше понять.

Тип \x -> join . x равен (Monad m) => (a -> m (m b)) -> (a -> m b), а тип \y -> y . (flip fmap) is (Functor f) => ((a -> b) -> f b) -> (f a -> c).

Версия 8.2.2 ghci разрешает определение h = join . (flip fmap).

Почему h имеет тип ((A -> B) -> A) -> (A -> B) -> B?

В частности, почему ограничения функтора и монады исчезают?Это действительно правильное и ожидаемое поведение?В качестве продолжения я также хотел бы спросить:

Почему оценка h (\f -> f u) (\x -> x + v) для целых чисел u и v дает u + 2v в каждом случае?

Ответы [ 2 ]

0 голосов
/ 31 мая 2018

Типы Выравнивание проще с >>>:

                          a   ->     b                       >>>
                                     b              -> c     ::
                          a   ->                       c   

Здесь мы имеем

join . flip fmap  ==  flip fmap >>> join

flip fmap :: Functor f => f a -> ((a -> b) -> f b )
join      :: Monad   m =>        (m          (m b)) -> m b
----------------------------------------------------------
flip fmap >>> join ::
 (Functor f, Monad m) =>  f a  ->                      m b   , ((a -> b) ->) ~ m, f ~ m
                   ::
 (Functor f, Monad f) =>  f a  ->                      f b   , f ~ ((a -> b) ->)
                   ::    ((a -> b) -> a) -> ((a -> b) -> b)

Простой, механический, обыденный.


К см. что он делает , определения комбинационных стилей, как правило, легче всего использовать,

(join . flip fmap) f g x =
 join (flip fmap f) g x =          -- join f x = f x x
 (`fmap` f) g g x =                -- f `fmap` g = f . g
 (g . f) g x 
 g (f g) x 

Так что нам не нужно x В конце концов (или мы?).Определения join и fmap для функций приведены на полях.Мы достигли

(join . flip fmap) f g = g (f g)   -- f :: (a -> b) -> a,  g :: a -> b 
                                   -- f g :: a  , g (f g) :: b

Другой путь начинается с типов, следуя правилу modus ponens,

            ((a -> b) -> a)    (a -> b)        --       f  g
            ---------------------------
(a -> b)                 a                     --  g   (f  g)
---------------------------------------
      b
0 голосов
/ 31 мая 2018

Короче говоря : из-за вывода типа Хаскелл знает, что m и f на самом деле являются частично экземплярированной стрелкой.

Вывод типа

Хорошо, давайте сделаем математику.Функция join . (flip fmap) - это, по сути, ваше заданное лямбда-выражение \x -> join . x с аргументом (flip fmap), поэтому:

h = (\x -> join . x) (flip fmap)

Теперь лямбда-выражение имеет тип:

(\x -> join . x) :: Monad m =>   (a -> m (m b)) -> (a -> m b)

Теперьаргумент flip fmap имеет тип:

flip fmap        :: Functor f => f c -> ((c -> d) -> f d)

(здесь мы используем c и d вместо a и b, чтобы избежать путаницы между двумя возможно различными типами).

Это означает, что тип flip fmap совпадает с типом аргумента лямбда-выражения, поэтому мы знаем, что:

  Monad m =>   a   -> m (m b)
~ Functor f => f c -> ((c -> d) -> f d)
---------------------------------------
a ~ f c, m (m b) ~ ((c -> d) -> f d)

Итак, теперь мы знаем, что a имеет тот же тип, что и f c (в этом смысл тильды ~).

Но нам нужно сделать несколько дополнительных вычислений:

  Monad m =>   m (m b)
~ Functor f => ((c -> d) -> f d)
--------------------------------
m ~ (->) (c -> d), m b ~ f d

Следовательно, мы знаем, что m - это то же самое, что и (->) (c -> d) (в основном это функция, в которой мы знаем этот тип ввода, здесь (c -> d), а тип вывода - это параметр типа m.

Значит, это m b ~ (c -> d) -> b ~ f d, значит, f ~ (->) (c -> d) и b ~ d. Дополнительным следствием является то, что с a ~ f c мы знаем, чтоa ~ (c -> d) -> c

Итак, перечислим, что мы получили:

f ~ m
m ~ (->) (c -> d)
b ~ d
a ~ (c -> d) -> c

Теперь мы можем «специализировать» типы как нашего лямбда-выражения, так и нашей функции flip fmap:

(\x -> join . x)
    :: (((c -> d) -> c) -> (c -> d) -> (c -> d) -> d) -> ((c -> d) -> c) -> (c -> d) -> d
flip fmap
    ::  ((c -> d) -> c) -> (c -> d) -> (c -> d) -> d

и тип flip fmap теперь идеально совпадают с типом аргумента лямбда-выражения.Таким образом, тип (\x -> join . x) (flip fmap) является типом результата типа лямбда-выражения, а именно:

(\x -> join . x) (flip fmap)
    :: ((c -> d) -> c) -> (c -> d) -> d

Но сейчас мы, конечно, еще не получили реализацию этой функции.Однако мы уже на шаг впереди.

Вывод реализации

Поскольку теперь мы знаем, что m ~ (->) (c -> d), мы знаем, что нам нужно искать экземпляр стрелки монады :

instance Monad ((->) r) where
    f >>= k = \ r -> k (f r) r

Таким образом, для данной функции f :: r -> a в качестве левого операнда и функции k :: a -> (r -> b) ~ a -> r -> b в качестве операнда мы создаем новую функцию, которая отображает переменную x вk применяется к f применяется к x и x.Таким образом, это способ выполнить некоторую предварительную обработку входной переменной x, а затем выполнить обработку как с учетом предварительной обработки, так и с исходным представлением (ну, это интерпретация * , которую может прочитать читатель-человек).использовать).

Сейчас join :: Monad m => m (m a) -> m a is реализовано как :

join :: Monad m => m (m a) -> m a
join x = x >>= id

Таким образом, для монады (->) r это означает, что мы реализуем это следующим образом:

-- specialized for `m ~ (->) a
join f = \r -> id (f r) r

, поскольку id :: a -> a (функция тождества) возвращает свой аргумент,мы можем еще больше упростить его до:

-- specialized for `m ~ (->) a
join f = \r -> (f r) r

или более чистого:

-- specialized for `m ~ (->) a
join f x = f x x

Так что в основном ему присваивается функция f, а затем он дважды применяет аргумент к этой функции.

Кроме того, мы знаем, что экземпляр Functor для типа стрелки определен как :

instance Functor ((->) r) where
    fmap = (.)

Так что он в основном используется как«постпроцессор» для результата функции: мы создаем новую функцию, которая будет выполнять постобработку с данной функцией.

Итак, теперь, когда мы достаточно специализировали функцию для заданного Functor / Monad, мы можем получить реализацию в виде:

-- alternative implementation
h = (.) (\f x -> f x x) (flip (.))

или с помощью дополнительных лямбда-выражений:

h = \a -> (\f x -> f x x) ((flip (.)) a)

, который мы теперь можем специализировать следующим образом:

h = \a -> (\f x -> f x x) ((\y z -> z . y) a)

-- apply a in the lambda expression
h = \a -> (\f x -> f x x) (\z -> z . a)

-- apply (\z -> z . a) in the first lambda expression
h = \a -> (\x -> (\z -> z . a) x x)

-- cleaning syntax
h a = (\x -> (\z -> z . a) x x)

-- cleaning syntax
h a x = (\z -> z . a) x x

-- apply lambda expression
h a x = (x . a) x

-- remove the (.) part
h a x = x (a x)

Таким образом, h в основном принимает два аргумента: a и x, затем выполняет функцию с помощью aв качестве функции и x в качестве параметра, и выход снова передается в функцию x.

Пример использования

В качестве примера использования вы используете:

h (\f -> f u) (\x -> x + v)

или лучше:

h (\f -> f u) (+v)

, чтобы мы могли проанализировать это следующим образом:

   h (\f -> f u) (+v)
-> (+v) ((\f -> f u) (+v))
-> (+v) ((+v) u)
-> (+v) (u+v)
-> ((u+v)+v)

Итак, мы добавляем u+v к v.

...