В чем разница между. (точка) и $ (знак доллара)?

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

14 ответов

Решение

$ Оператор для избежания скобок. Все, что появляется после этого, будет иметь приоритет над всем, что приходит раньше.

Например, предположим, у вас есть строка, которая гласит:

putStrLn (show (1 + 1))

Если вы хотите избавиться от этих скобок, любая из следующих строк также сделает то же самое:

putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

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

Возвращаясь к тому же примеру:

putStrLn (show (1 + 1))
  1. (1 + 1) не имеет ввода, и поэтому не может быть использован с . оператор.
  2. show может взять Int и вернуть String,
  3. putStrLn может взять String и вернуть IO (),

Вы можете цепи show в putStrLn как это:

(putStrLn . show) (1 + 1)

Если это слишком много скобок на ваш вкус, избавьтесь от них с помощью $ оператор:

putStrLn . show $ 1 + 1

Они имеют разные типы и разные определения:

infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x

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

В некоторых случаях они взаимозаменяемы, но в целом это не так. Типичный пример, где они находятся:

f $ g $ h $ x

==>

f . g . h $ x

Другими словами, в цепочке $s, все, кроме последнего, можно заменить на .

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

id :: a -> a
id x = x

В то время как ($) выглядит так:

($) :: (a -> b) -> (a -> b)
($) = id

Обратите внимание, что я намеренно добавил дополнительные скобки в сигнатуру типа.

Использование ($) обычно можно устранить, добавив круглые скобки (если оператор не используется в разделе). Например: f $ g x становится f (g x),

Использование (.) часто немного сложнее заменить; обычно им требуется лямбда или введение явного параметра функции. Например:

f = g . h

становится

f x = (g . h) x

становится

f x = g (h x)

Надеюсь это поможет!

($) позволяет объединять функции без добавления скобок для управления порядком оценки:

Prelude> head (tail "asdf")
's'

Prelude> head $ tail "asdf"
's'

Оператор сочинения (.) создает новую функцию без указания аргументов:

Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'

Prelude> let second = head . tail
Prelude> second "asdf"
's'

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

Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"

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

Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"

Наконец, композиция позволяет нам избежать лямбды:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"

Краткая и сладкая версия:

  • ($) вызывает функцию, которая является ее левым аргументом для значения, которое является его правым аргументом.
  • (.) Составляет функцию, которая является ее левым аргументом функции, которая является ее правым аргументом.

Одно приложение, которое полезно и заняло у меня некоторое время, чтобы разобраться из очень короткого описания, которое вы изучили:

f $ x = f x

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

Зачем кому-то делать это? Для списков функций, например. И то и другое:

map (++", world") ["hello","goodbye"]`

а также:

map ($ 3) [(4+),(3*)]

короче чем map (\x -> x ++ ", world") ... или же map (\f -> f 3) ..., Очевидно, что последние варианты были бы более удобочитаемыми для большинства людей.

Haskell: разница между . (точка) и $ (знак доллара)

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

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

(.) является составной функцией. Так

result = (f . g) x

это то же самое, что создание функции, которая передает результат своего аргумента, переданного g на f,

h = \x -> f (g x)
result = h x

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

result = f $ g x

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

h = f
g_x = g x
result = h g_x

или более кратко:

result = f (g x)

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

Читать источник

Вот источник для (.):

-- | Function composition.
{-# INLINE (.) #-}
-- Make sure it has TWO args only on the left, so that it inlines
-- when applied to two functions, even if there is no final argument
(.)    :: (b -> c) -> (a -> b) -> a -> c
(.) f g = \x -> f (g x)

И вот источник ($):

-- | Application operator.  This operator is redundant, since ordinary
-- application @(f x)@ means the same as @(f '$' x)@. However, '$' has
-- low, right-associative binding precedence, so it sometimes allows
-- parentheses to be omitted; for example:
--
-- >     f $ g $ h x  =  f (g (h x))
--
-- It is also useful in higher-order situations, such as @'map' ('$' 0) xs@,
-- or @'Data.List.zipWith' ('$') fs xs@.
{-# INLINE ($) #-}
($)                     :: (a -> b) -> a -> b
f $ x                   =  f x

Когда использовать:

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

Используйте приложение, когда вы предоставляете все аргументы для полной оценки.

Так что для нашего примера было бы семантически предпочтительнее сделать

f $ g x

когда у нас есть x (или скорее, gаргументы), и сделать:

f . g

когда мы не

Мое правило простое (я тоже новичок):

  • не использовать . если вы хотите передать параметр (вызвать функцию), и
  • не использовать $ если еще нет параметров (составьте функцию)

То есть

show $ head [1, 2]

но никогда:

show . head [1, 2]

... или вы могли бы избежать . а также $ конструкции с использованием трубопроводов:

third xs = xs |> tail |> tail |> head

Это после того, как вы добавили в вспомогательную функцию:

(|>) x y = y x

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

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

а также

:t ($)
($) :: (a -> b) -> a -> b

Просто не забудьте использовать :t обильно, и оберните ваших операторов в ()!

Наиболее важной частью $ является то, что он имеет самый низкий приоритет оператора.

Если вы введете информацию, вы увидите это:

λ> :info ($)
($) :: (a -> b) -> a -> b
    -- Defined in ‘GHC.Base’
infixr 0 $

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

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

Так, например, работает:

λ> head . sort $ "example"
λ> e

но это не так:

λ> head . sort "example"

так как. имеет более низкий приоритет, чем сортировка, а тип (сортировка "пример") - [Char]

λ> :type (sort "example")
(sort "example") :: [Char]

Но. ожидает две функции, и нет удобного короткого способа сделать это из-за порядка операций сортировки и.

В этом посте объясняется, почему инфиксные функции (операторы) (.) и ($) различны.

Самая запутанная часть — это использование оператора доллара ($) в композиции функций, применяемой к входным данным.

Тип и определение оператора (.):

      (.) :: (b -> c) -> (a -> c) -> a -> c
f . g = \x -> f (g x)
-- It should really be defined as follows to make it clear of the syntax,
-- but looks weird since most functions aren't defined like this.
(f . g) x = f (g x)

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

      -- The part "(map (*5))" enclosed with parentheses,
-- is to explictly indicate it's partially applied as a function that takes a list as the argument. 
replicate 3 . reverse . (map (*5)) [1..2]

Но должно быть так?

      replicate 3 . reverse $ (map (*5)) [1..2]
-- Or 
replicate 3 . reverse . (map (*5)) $ [1..2]

Предположим, у нас есть функция, принимающая три аргумента, с именем «sumThreeNums» как

      sumThreeNums :: Num a => a -> a -> a -> a
sumThreeNums n1 n2 n3 = n1 + n2 + n3

Он принимает три аргумента, как и оператор точки (.), определенный для композиции функций.

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

      3 `sumThreeNums` 2 4

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

      (3 `sumThreeNums` 2) 4

Здесь в игру вступает оператор ($), мы можем записать приведенное выше выражение как:

      3 `sumThreeNums` 2 $ 4

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

Подводя итог, вот почему мы написали композицию функции, применяемую к входу, которая выглядит так:

      (replicate 3 . reverse) (map (*5) [1..2])
-- Or
(replicate 3 . reverse . map (*5)) [1..2]
-- Become as:
replicate 3 . reverse $ map (*5) [1..2]
-- Or 
replicate 3 . reverse . map (*5) $ [1..2]

Все остальные ответы довольно хороши. Но есть важная деталь юзабилити о том, как ghc обрабатывает $, о том, что средство проверки типов ghc допускает использование типов с более высоким рангом / квантификацией. Если вы посмотрите на тип $ id например, вы обнаружите, что она возьмет функцию, аргумент которой сам по себе является полиморфной функцией. Такие мелочи не имеют такой же гибкости с эквивалентным оператором расстройства. (Это на самом деле заставляет задуматься, заслуживает ли $! Такой же обработки или нет)

Я думаю, что короткий пример того, где вы будете использовать . и не $ помог бы уточнить вещи.

double x = x * 2
triple x = x * 3
times6 = double . triple

:i times6
times6 :: Num c => c -> c

Обратите внимание, что times6 это функция, которая создается из композиции функций.

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