В чем разница между. (точка) и $ (знак доллара)?
В чем разница между точкой (.)
и знак доллара ($)
?. Насколько я понимаю, они оба являются синтаксическим сахаром для того, чтобы не использовать скобки.
14 ответов
$
Оператор для избежания скобок. Все, что появляется после этого, будет иметь приоритет над всем, что приходит раньше.
Например, предположим, у вас есть строка, которая гласит:
putStrLn (show (1 + 1))
Если вы хотите избавиться от этих скобок, любая из следующих строк также сделает то же самое:
putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1
Основная цель .
Оператор не для того, чтобы избежать скобок, но для цепочки функций. Это позволяет связать вывод того, что появляется справа, с вводом того, что появляется слева. Обычно это также приводит к уменьшению скобок, но работает по-другому.
Возвращаясь к тому же примеру:
putStrLn (show (1 + 1))
(1 + 1)
не имеет ввода, и поэтому не может быть использован с.
оператор.show
может взятьInt
и вернутьString
,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
это функция, которая создается из композиции функций.