Что происходит с точкой (.) В 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
,