В чем разница между точкой (.)
и знаком доллара ($)
?
Насколько я понимаю, они оба являются синтаксическим сахаром для того, чтобы не использовать скобки.
В чем разница между точкой (.)
и знаком доллара ($)
?
Насколько я понимаю, они оба являются синтаксическим сахаром для того, чтобы не использовать скобки.
Ответы:
$
Оператор для избежания круглых скобок. Все, что появляется после этого, будет иметь приоритет над всем, что приходит раньше.
Например, предположим, у вас есть строка, которая гласит:
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
putStrLn . show . (+1) $ 1
будет эквивалентно. Вы правы в том, что большинство (всех?) Инфиксных операторов являются функциями.
map ($3)
. Я имею в виду, я в основном использую, $
чтобы избежать скобок, но это не то, для чего они здесь.
map ($3)
это функция типа Num a => [(a->b)] -> [b]
. Он принимает список функций, принимающих числа, применяет 3 ко всем из них и собирает результаты.
Они имеют разные типы и разные определения:
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 все, кроме последнего, можно заменить на.
x
была функция? Можете ли вы использовать .
в качестве последнего?
x
в этом контексте, тогда да - но тогда «последний» будет применяться к чему-то другому, кроме x
. Если вы не подаете заявку x
, то это ничем не отличается от x
ценности.
Также обратите внимание, что ($)
это функция идентификации, специализированная для типов функций . Идентификационная функция выглядит так:
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'
Оператор 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"
Одно приложение, которое полезно и заняло у меня некоторое время, чтобы разобраться из очень короткого описания, которое вы изучили :
f $ x = f x
и заключение в скобки правой части выражения, содержащего инфиксный оператор, преобразует его в префиксную функцию, которую можно написать ($ 3) (4+)
аналогично (++", world") "hello"
.
Зачем кому-то это делать? Для списков функций, например. Обе:
map (++", world") ["hello","goodbye"]`
а также:
map ($ 3) [(4+),(3*)]
короче map (\x -> x ++ ", world") ...
или map (\f -> f 3) ...
. Очевидно, что последние варианты были бы более удобочитаемыми для большинства людей.
$3
без места. Если Template Haskell включен, это будет проанализировано как соединение, тогда как $ 3
всегда означает, что вы сказали. В целом, похоже, в Haskell существует тенденция «красть» биты синтаксиса, настаивая на том, что у определенных операторов есть пробелы вокруг них, которые должны рассматриваться как таковые.
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
$
оператор на самом деле работает больше как F # 'S , <|
чем это делает |>
, как правило , в Haskell вы бы написать вышеуказанную функцию: third xs = head $ tail $ tail $ xs
или , возможно , даже как third = head . tail . tail
, что в F # -стиль синтаксис будет что - то вроде этого:let third = List.head << List.tail << List.tail
Отличный способ узнать больше о чем угодно (о любой функции) - помнить, что все является функцией! Эта общая мантра помогает, но в определенных случаях, таких как операторы, она помогает запомнить этот маленький трюк:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
а также
:t ($)
($) :: (a -> b) -> a -> b
Просто не забывайте использовать :t
свободно, и заверните свои операторы в ()
!
Я думаю, что короткий пример того, где вы будете использовать, .
а не $
поможет прояснить ситуацию.
double x = x * 2
triple x = x * 3
times6 = double . triple
:i times6
times6 :: Num c => c -> c
Обратите внимание, что times6
это функция, которая создается из композиции функций.
Все остальные ответы довольно хороши. Но есть важная деталь юзабилити о том, как ghc обрабатывает $, о том, что средство проверки типов ghc позволяет устанавливать типы с более высоким рангом / квантифицированными типами. Если вы посмотрите на тип, $ id
например, вы обнаружите, что он возьмет функцию, аргумент которой сам по себе является полиморфной функцией. Такие мелочи не имеют такой же гибкости с эквивалентным оператором расстройства. (Это на самом деле заставляет задуматься, заслуживает ли $! Того же обращения или нет)
Самая важная часть о $ - это то, что он имеет самый низкий приоритет оператора.
Если вы введете информацию, вы увидите это:
λ> :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 и.