Как я могу понять "(.). (.)"?

Я считаю, что я понимаю fmap . fmap для Фунтеров, но по функциям у меня болит голова уже несколько месяцев.

Я видел, что вы можете просто применить определение (.) к (.) . (.), но я забыл, как это сделать.
Когда я сам это пробовал, это всегда получается неправильно:

(.) f g = \x -> f (g x)
(.) (.) (.) = \x -> (.) ((.) x)
\x f -> (.) ((.) x) f
\x f y  -> (((.)(f y)) x)
\x f y g-> (((.)(f y) g) x)
\x f y g-> ((f (g y)) x)
\x f y g-> ((f (g y)) x):: t2 -> (t1 -> t2 -> t) -> t3 -> (t3 -> t1) -> t

Если "просто применить определение" - единственный способ сделать это, как кто-нибудь придумал (.) . (.)?
Должно быть какое-то более глубокое понимание или интуиция, которую мне не хватает.

Ответы

Ответ 1

Вы также можете использовать свое понимание fmap . fmap.

Если у вас есть два Functor foo и bar, то

fmap . fmap :: (a -> b) -> foo (bar a) -> foo (bar b)

fmap . fmap берет функцию и производит индуцированную функцию для композиции двух Functor s.

Теперь для любого типа t, (->) t является Functor, а fmap для этого Functor - (.).

So (.) . (.) есть fmap . fmap для случая, когда два Functor являются (->) s и (->) t, и, следовательно,

(.) . (.) :: (a -> b) -> ((->) s) ((->) t a) -> ((->) s) ((->) t b)
          =  (a -> b) -> (s -> (t -> a))     -> (s -> (t -> b))
          =  (a -> b) -> (s -> t -> a)       -> (s -> t -> b)

он "составляет" функцию f :: a -> b с функцией из двух аргументов, g :: s -> t -> a,

((.) . (.)) f g = \x y -> f (g x y)

Это представление также дает понять, что и как, шаблон распространяется на функции, принимающие больше аргументов,

(.) . (.) . (.) :: (a -> b) -> (s -> t -> u -> a) -> (s -> t -> u -> b)

и др.

Ответ 2

Приступая к (.) . (.) на самом деле довольно просто, это интуиция за тем, что она делает, что довольно сложно понять.

(.) позволяет вам очень далеко, переписывая выражение в вычисления типа "pipe" (подумайте о | в оболочке). Тем не менее, становится неудобно использовать, как только вы пытаетесь создать функцию, которая принимает несколько аргументов с помощью функции, которая принимает только одну. В качестве примера давайте определим concatMap:

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)

Избавление от xs - это стандартная операция:

concatMap f = concat . map f

Однако нет "приятного" способа избавиться от f. Это вызвано тем фактом, что map принимает два аргумента, и мы хотели бы применить concat к его окончательному результату.

Вы можете, конечно, применить некоторые трюки с точки зрения и уйти просто с помощью (.):

concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map

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

-- .: is fairly standard name for this combinator
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x y = f (g x y)

concatMap = concat .: map

Хорошо, что это для мотивации. Позвольте перейти к беспроблемному бизнесу.

(.:) = \f g x y -> f (g x y)
     = \f g x y -> f ((g x) y)
     = \f g x y -> f . g x $ y
     = \f g x   -> f . g x

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

     = \f g x   -> (.) f (g x)
     = \f g x   -> (.) f . g $ x
     = \f g     -> (.) f . g
     = \f g     -> (.) ((.) f) g
     = \f       -> (.) ((.) f)
     = \f       -> (.) . (.) $ f
     =             (.) . (.)

Что касается интуиции, там очень хорошая статья, которую вы должны прочитать. Я перефразирую часть (.):

Давайте еще раз подумаем о том, что должен сделать наш комбинатор: он должен применить f к результату результата g (я использовал конечный результат в части перед целенаправленным, это действительно то, что вы получаете, когда вы полностью применять - модулировать переменные типа с другим типом функции - функцию g, результатом здесь является просто приложение g x для некоторого x).

Что это значит для нас применить f к результату g? Ну, как только мы применим g к некоторому значению, мы возьмем результат и применим к нему f. Звучит знакомо: что делает (.).

result :: (b -> c) -> ((a -> b) -> (a -> c))
result = (.)

Теперь оказывается, что композиция (наше слово) этих комбинаторов - это просто композиция функции, то есть:

(.:) = result . result -- the result of result

Ответ 3

Ваше решение расходится, когда вы вводите y. Это должно быть

\x f y -> ((.) ((.) x) f) y     :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) ((.) x) f) y z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) x (f y)) z     :: (c -> d) -> (a -> b -> c) -> a -> b -> d
-- Or alternately:
\x f y z -> (x . f y) z         :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> (x (f y z))         :: (c -> d) -> (a -> b -> c) -> a -> b -> d

Что соответствует оригинальной сигнатуре типа: (.) . (.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d

(Проще всего сделать расширение в ghci, где вы можете проверить каждый шаг с помощью :t expression)

Edit:

Более глубокая интуиция такова:

(.) просто определяется как

\f g -> \x -> f (g x)

Что мы можем упростить до

\f g x -> f (g x)

Поэтому, когда вы предоставляете ему два аргумента, он берет и все еще нуждается в другом аргументе для разрешения. Каждый раз, когда вы используете (.) с двумя аргументами, вы создаете "необходимость" для еще одного аргумента.

(.) . (.), разумеется, просто (.) (.) (.), поэтому разложим его:

(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))

Мы можем бета-уменьшить на f0 и g0 (но у нас нет x0!):

\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0) 

Замените второе выражение для f1...

\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1) 

Теперь он "откидывается назад"! (бета-сокращение на f2):
Это интересный шаг - x0 заменяется на f2. Это означает, что x, который мог быть данными, вместо этого является функцией. Это то, что (.) . (.) предоставляет - "необходимость" для дополнительного аргумента.

\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1) 

Это начинает выглядеть нормально... Пусть бета-сокращение в последний раз (на g2):

\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))

Итак, мы остались просто

\x0 g1 x1 x2 -> x0 ((g1 x1) x2)

где аргументы хорошо по порядку.

Ответ 4

Итак, это то, что я получаю, когда делаю несколько более инкрементное расширение

(.) f g   = \x -> f (g x)
(.) . g   = \x -> (.) (g x)
          = \x -> \y -> (.) (g x) y
          = \x -> \y -> \z -> (g x) (y z)
          = \x y z -> (g x) (y z)
(.) . (.) = \x y z -> ((.) x) (y z)
          = \x y z -> \k -> x (y z k)
          = \x y z k -> x (y z k)

Что, согласно ghci, имеет правильный тип

Prelude> :t (.) . (.)
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
Prelude> :t \x y z k -> x (y z k)
\x y z k -> x (y z k)
  :: (t1 -> t) -> (t2 -> t3 -> t1) -> t2 -> t3 -> t
Prelude> 

Пока я не знаю происхождения этого комбинатора, вполне вероятно, что это было разработанный для использования в комбинаторной логике, где вы строго работаете с комбинаторами, поэтому вы не можете определить вещи, используя более удобные лямбда-выражения. Может быть какая-то интуиция, которая идет об этом, но я ее не нашел. Скорее всего, вы бы разработали некоторый уровень интуиции, если вам нужно было сделать это достаточно.

Ответ 5

Проще всего написать уравнения, комбинатор-стиль, вместо лямбда-выражений: a b c = (\x -> ... body ...) эквивалентно a b c x = ... body ..., а порок наоборот, при условии, что x не появляется среди {a,b,c}. Таким образом,

-- _B = (.)  

_B f g x = f (g x)
_B _B _B f g x y = _B (_B f) g x y
                 = (_B f) (g x) y
                 = _B f (g x) y
                 = f ((g x) y)
                 = f (g x y)

Вы обнаружите это, если, учитывая f (g x y), вы хотите преобразовать его в комбинационную форму (избавиться от всех круглых скобок и переменной повторы). Затем вы применяете шаблоны, соответствующие определениям комбинаторов, надеясь проследить этот вывод назад. Это намного меньше механического/автоматического.