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

709

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

Насколько я понимаю, они оба являются синтаксическим сахаром для того, чтобы не использовать скобки.

Rabarberski
источник

Ответы:

1226

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

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

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
Майкл Стил
источник
55
На самом деле, так как + тоже функция, вы не можете сделать ее префиксом, а затем составить ее, как `putStrLn. Показать . (+) 1 1 `Не то чтобы это было понятнее, но я имею в виду ... ты мог бы, верно?
CodexArcanum
4
@CodexArcanum В этом примере нечто подобное putStrLn . show . (+1) $ 1будет эквивалентно. Вы правы в том, что большинство (всех?) Инфиксных операторов являются функциями.
Майкл Стил
79
Я удивляюсь, почему никто никогда не упоминает об использовании как map ($3). Я имею в виду, я в основном использую, $чтобы избежать скобок, но это не то, для чего они здесь.
Кубик
43
map ($3)это функция типа Num a => [(a->b)] -> [b]. Он принимает список функций, принимающих числа, применяет 3 ко всем из них и собирает результаты.
Куб
21
Вы должны быть осторожны при использовании $ с другими операторами. «x + f (y + z)» - это не то же самое, что «x + f $ y + z», поскольку последнее фактически означает «(x + f) (y + z)» (т. е. сумма x и f равна рассматривается как функция).
Пол Джонсон
186

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

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 все, кроме последнего, можно заменить на.

GS - извиниться перед Моникой
источник
1
Что если бы xбыла функция? Можете ли вы использовать .в качестве последнего?
richizy
3
@richizy, если вы на самом деле применяете xв этом контексте, тогда да - но тогда «последний» будет применяться к чему-то другому, кроме x. Если вы не подаете заявку x, то это ничем не отличается от xценности.
GS -
124

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

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)

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

Мартейн
источник
«Обратите внимание, что я намеренно добавил дополнительные скобки в сигнатуру типа». Я в замешательстве ... почему ты это сделал?
Матин Улхак
3
@MateenUlhaq Тип ($): (a -> b) -> a -> b, что аналогично (a -> b) -> (a -> b), но дополнительные скобки здесь добавляют некоторые ясность.
Руди
2
О, я полагаю Я думал об этом как о функции двух аргументов ... но из-за карри это точно соответствует функции, которая возвращает функцию.
Матин Улхак
78

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

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

Prelude> head $ tail "asdf"
's'

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

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"
softmechanics
источник
3
Если в стеке потока есть комбинированная функция, я бы предпочел, чтобы ответ сочетал два предыдущих объяснения с примером из этого ответа.
Chris.Q
59

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

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

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

f $ x = f x

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

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

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

а также:

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

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

Christoph
источник
14
Кстати, я бы посоветовал не использовать $3без места. Если Template Haskell включен, это будет проанализировано как соединение, тогда как $ 3всегда означает, что вы сказали. В целом, похоже, в Haskell существует тенденция «красть» биты синтаксиса, настаивая на том, что у определенных операторов есть пробелы вокруг них, которые должны рассматриваться как таковые.
GS - Приносим свои извинения Монике
1
Мне потребовалось
Casebash
18

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

когда мы не

Аарон Холл
источник
12

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

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

Это

show $ head [1, 2]

но никогда:

show . head [1, 2]
halacsy
источник
3
Хорошая эвристика, но можно использовать больше примеров
Зои Хьюлл
12

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

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

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

(|>) x y = y x
user1721780
источник
2
Да,> оператор F # конвейера.
user1721780
6
Одна вещь , чтобы отметить здесь, что в Haskell $оператор на самом деле работает больше как F # 'S , <|чем это делает |>, как правило , в Haskell вы бы написать вышеуказанную функцию: third xs = head $ tail $ tail $ xsили , возможно , даже как third = head . tail . tail, что в F # -стиль синтаксис будет что - то вроде этого:let third = List.head << List.tail << List.tail
Электрический кофе
1
Зачем добавлять вспомогательную функцию, чтобы Haskell выглядел как F #? -1
Викингстев
10
Перевернутый $уже доступны, и это называется & hackage.haskell.org/package/base-4.8.0.0/docs/...
погладить
11

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

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

а также

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

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

лол
источник
0

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

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

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

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

Бреннан Чунг
источник
0

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

Картер Тацио Шонвальд
источник
0

Самая важная часть о $ - это то, что он имеет самый низкий приоритет оператора.

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

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

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

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

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

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

но это не

λ> head . sort "example"

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

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

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

user2415706
источник