В чем разница между . (точка) и $ (знак доллара)? - PullRequest
665 голосов
/ 02 июня 2009

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

Ответы [ 12 ]

1160 голосов
/ 18 августа 2009

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

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

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
177 голосов
/ 02 июня 2009

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

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

121 голосов
/ 02 июня 2009

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

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)

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

77 голосов
/ 07 января 2010

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

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"
58 голосов
/ 11 июня 2009

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

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

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

f $ x = f x

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

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

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

и

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

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

12 голосов
/ 04 декабря 2012

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

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

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

(|>) x y = y x
11 голосов
/ 15 апреля 2015

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

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

и

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

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

11 голосов
/ 20 августа 2013

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

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

То есть

show $ head [1, 2]

но никогда:

show . head [1, 2]
9 голосов
/ 10 ноября 2017

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

когда мы этого не сделаем.

...