Почему эта конструкция со знаком доллара не работает?

Да, еще один вопрос со знаком доллара. Извините... (я воспользовался поиском!)

Мой преподаватель курса функционального программирования сказал нам, что знак доллара «как бы добавляет открывающую скобку, а затем закрывающую в конце» (это очень приблизительно описано здесь в более или менее том же образом). Так

fibs = 0 : 1 : zipWith (+) fibs $ tail fibs

должно быть эквивалентно

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

Ну, это не так. Второе компилируется нормально, первое выдает ошибку:

jkjj.hs:1:8:
    Couldn't match expected type `[a1] -> [a1]' with actual type `[a0]'
    The first argument of ($) takes one argument,
    but its type `[a0]' has none
    In the expression: 0 : 1 : zipWith (+) fibs $ tail fibs
    In an equation for `fibs':
        fibs = 0 : 1 : zipWith (+) fibs $ tail fibs

fibonacci.hs:1:16:
    Couldn't match expected type `[a0]' with actual type `[a1] -> [a1]'
    In the return type of a call of `zipWith'
    Probable cause: `zipWith' is applied to too few arguments
    In the second argument of `(:)', namely `zipWith (+) fibs'
    In the second argument of `(:)', namely `1 : zipWith (+) fibs'

И, конечно же, поскольку $ — это функция, такие вещи, как:

fibs = 0 : 1 $ zipWith (+) fibs (tail fibs)

не сработает, так что, по крайней мере, объяснение, которое дал мой профессор, было чрезмерным упрощением. При написании этого поста я постарался расставить скобки так, чтобы ошибка была та же. Я получил:

fibs = (0 : 1 : zipWith (+) fibs) $ tail fibs

и

fibs = (0 : 1 : zipWith (+) fibs) (хвост fibs)

которые оба дали мне точно такое же сообщение об ошибке (за исключением номеров столбцов, конечно). Почему это? Является ли a b $ c d эквивалентом (ab) (c d), а не a b (c d)? Я думаю, что все это связано с приоритетом функций и/или ассоциативностью, но я не знаю специфики. Я не знаю, как вы можете увидеть, какой уровень приоритета имеет функция (кроме перебора множества комбинаций), и я также не могу найти его в Google.

Я надеюсь, что кто-то может помочь мне понять это!


person Ruben    schedule 28.01.2014    source источник
comment
Ну, в статье FP, которую вы цитируете, она гласит: A $ интерпретируется как: Далее следует последний аргумент функции, которую вы вызываете., поэтому вопрос в том, что это за функция, которую я м звоню? Очевидно, выражение ушло от $, как вы выяснили.   -  person Ingo    schedule 28.01.2014
comment
Истинный! Но zipWith казался более очевидным выбором для «функции, которую вы вызываете».   -  person Ruben    schedule 28.01.2014


Ответы (3)


Это вопрос приоритета. Здесь у вас есть два инфиксных оператора, : и $. Как инфиксный оператор, : имеет более высокий приоритет, чем $, поэтому он связывает более тесно. Вы можете спросить о приоритете в ghci

>> :i :
data [] a = ... | a : [a]        -- Defined in `GHC.Types`
infixr 5 :

>> :i $
($) :: (a -> b) -> a -> b
infixr 0 $

infixr означает, что оператор группируется справа (так что выражение a + b + c интерпретируется как a + (b + c)), а число дает приоритет (больше = более тесная связь).

Кроме того, вам нужно знать, что приложение-функция имеет наивысший приоритет (наиболее тесную привязку). Итак, в ваших двух выражениях это одно

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

сгруппирован как

fibs = 0 : (1 : (zipWith (+) fibs (tail fibs)))

тогда как это

fibs = 0 : 1 : zipWith (+) fibs $ tail fibs

сгруппирован как

fibs = (0 : (1 : (zipWith (+) fibs))) $ (tail fibs)

что дает вам ошибку, потому что выражение слева от $ должно быть функцией, но в вашем случае это список.

person Chris Taylor    schedule 28.01.2014

Да, a b $ c d эквивалентно (a b) (c d). Это связано с тем, что знак доллара не является синтаксической конструкцией: это инфиксный оператор, как и любой другой оператор, и он должен подчиняться тем же правилам. Просто $ применяет функцию и имеет очень низкий приоритет, что определяет ее поведение.

Вы спросили, как найти приоритет оператора. Вы можете использовать :info в GHCi:

ghci> :info $
($) :: (a -> b) -> a -> b   -- Defined in `GHC.Base'
infixr 0 $

Обратите внимание на последнюю строку. Это говорит о том, что это правоассоциативный оператор с приоритетом 0.
(Приоритеты варьируются от 0 до 9.)

Спросив GHCi о :, вы получите следующее:

ghci> :info :
data [] a = ... | a : [a]   -- Defined in `GHC.Types'
infixr 5 :

Таким образом, : также является правоассоциативным оператором, но имеет приоритет 5. Поскольку : имеет более высокий приоритет по сравнению с $, он будет сгруппирован в левый операнд $.

person icktoofay    schedule 28.01.2014
comment
Также обратите внимание, что оператор : имеет приоритет 5, что, по-видимому, является конкретной проблемой, с которой они сталкиваются. - person Xymostech; 28.01.2014
comment
Потрясающе, именно то, что я искал! - person Ruben; 28.01.2014

Правильно: вы узнали, что

a b ! c d $ e f % g h

может быть прочитано как размещение скобок вокруг выражений с обеих сторон, т.е.

(a b ! c d) (e f % g h)

Это справедливо до тех пор, пока вы используете только то, что можете определить сами в стандартном Haskell: функции и инфиксные операторы1.

Где это не совсем работает, так это когда вы также рассматриваете синтаксис if, case и т. д. В частности, и именно здесь объяснение вашего профессора становится более полезным, лямбды "жадны" справа от них:

   \x -> f $ a + x

означает \x -> f (a + x), а не (\x -> f) (a + x). Но «круглые скобки с обеих сторон» по-прежнему имеют смысл, вы просто не должны включать лямбда-стрелку:

   \x -> f . g $ a + x

означает \x -> (f . g) (a + x), а не \x -> f . g (a + x).


1В принципе можно определить пользовательский инфиксный оператор с таким же низким приоритетом, как $, но это может привести к путанице.

person leftaroundabout    schedule 28.01.2014