Что происходит с точкой (.) В Haskell?

У меня возникают проблемы с пониманием того, как работает оператор композиции функций в Haskell. Я понимаю это в простых случаях, когда вы используете только функции, которые принимают один аргумент, но я не понимаю, что происходит, когда вы используете его для нескольких функций, каждая из которых принимает более одного аргумента.

Рассмотрим эту функцию:

f1 = (*) . (+) 2 $ 1

Все, что делает эта функция, так это то, что она берет число и умножает его на три, так что это тоже самое

f2 = (*) $ (+) 2 1

а также

f3 = (*) 3

Я точно понимаю, что происходит в f2, но я не понимаю, что происходит в f1, что делает их одинаковыми. (.) Принимает две функции и значение, каждая из функций может принимать только один аргумент, поэтому в этом случае (*) и (+) должны быть частично применены, чтобы все работало. Я не понимаю, к чему они применяются и как это передается через цепочку функций.

В f2 (+) сначала применяется обе функции, производящие значение, которое частично применяется к (*), создавая функцию. В f1 невозможно (+) назначить оба значения, так как (.) Требует функции в качестве входных данных, но они одинаковы. Я этого не понимаю.

Я надеюсь, вы понимаете, что у меня проблемы с пониманием. Заранее спасибо!

4 ответа

Решение

С точки зрения порядка операций, Haskell обычно идет: круглые скобки, приложения функций, операторы, специальные формы. Так fact 8 + case h (f 3 + g 1) of Nothing -> 2 будет разбираться как:

(+)
    fact
        8
    case
        h
            (+)
                f
                    3
                g
                    1
            Nothing
            2

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

Деревья синтаксического анализа ваших двух функций:

f1 = (*) . (+) 2 $ 1
($)
    (.)
        (*)
        (+)
            2
    1

f2 = (*) $ (+) 2 1
($)
    (*)
    (+)
        2
        1

Мы, конечно, знаем, что a $ b = a b это определение $ а также a . b = \x -> a (b x) это определение ., Из этого мы видим глубокую идентичность, которая:

(a . b) c == a $ b c

($ функцию можно рассматривать как "ленивый ном", я говорю, что a будет использовать весь остаток выражения в качестве аргумента.)

Другими словами, приложение-функция преобразует . в $, Вот и все, что вам нужно, чтобы понять приведенное выше выражение:

(*) . (+) 2 $ 1
--> ((*) . (+) 2) 1  [use explicit () instead of the precedence of $]
--> (*) $ (+) 2 1  [your expression]

Интуитивно (*) . (2 +) выше говорит "взять вывод (2 +) и кормить его на входе (*), Если вы кормите 4 в результате первое, что происходит, это то, что он вычисляет 2 + 4, вывод 6, а затем это подается на вход (*) делать (6 *),

Просмотр приоритета оператора может помочь:

f1 = ((*) . ((+) 2)) $ 1

Другими словами, это (*) из (2+) - то есть, \x -> (*) ((2+) x) - на 1, делая это (*) ((2+) 1), который (*) (2 + 1), который (*) 3, и это f2,

(.) это просто еще одна функция в Haskell:

(.) f g x = f (g x)

и то и другое f а также g Кстати, может ожидать более одного аргумента. Это означает, что

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

Это ключ: любой (последний) аргумент в левой части эквационального определения Haskell может идти в правую часть, где он становится (первым) лямбда-параметром.

Так,

f1 = (*) . (+) 2 $ 1
   = ((*) . ((+) 2)) 1            -- this is how it is really read
   = (\x -> (*) ( ((+) 2) x )) 1
   =        (*) ( ((+) 2) 1 )
   =        (*) (  (+) 2  1 )
   =        (*) 3

На самом деле никаких загадок нет.

Как указывает @minitech, сначала поймите порядок операций, то есть:

f1 = ((*) . ((+) 2)) $ 1

В общем, для бинарных операций f а также gэто равенство верно:

((f . (g x)) $ y) z = f (g x y) z

Мы просто используем определение оператора compose: (f . g) a = f (g a),

 (f . (g x)) $ y = (f . (g x)) y = f (g x y)

так что:

 ((f . (g x)) $ y) z = f (g x y) z

Теперь подставим f = (*), g = (+), x = 2 а также y = 1,

Другие вопросы по тегам