Qual é a diferença entre o ponto (.)
e o cifrão ($)
?
Pelo que entendi, ambos são açúcares sintáticos por não precisarem usar parênteses.
Qual é a diferença entre o ponto (.)
e o cifrão ($)
?
Pelo que entendi, ambos são açúcares sintáticos por não precisarem usar parênteses.
Respostas:
O $
operador é para evitar parênteses. Qualquer coisa que aparecer depois terá precedência sobre qualquer coisa que vem antes.
Por exemplo, digamos que você tenha uma linha que diz:
putStrLn (show (1 + 1))
Se você deseja se livrar desses parênteses, qualquer uma das seguintes linhas também faria o mesmo:
putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1
O objetivo principal do .
operador não é evitar parênteses, mas encadear funções. Permite vincular a saída do que aparecer à direita à entrada do que aparecer à esquerda. Isso geralmente também resulta em menos parênteses, mas funciona de maneira diferente.
Voltando ao mesmo exemplo:
putStrLn (show (1 + 1))
(1 + 1)
não possui uma entrada e, portanto, não pode ser usado com o .
operador.show
pode pegar um Int
e retornar um String
.putStrLn
pode pegar um String
e retornar um IO ()
.Você pode encadear show
da putStrLn
seguinte maneira:
(putStrLn . show) (1 + 1)
Se houver muitos parênteses ao seu gosto, livre-se deles com o $
operador:
putStrLn . show $ 1 + 1
putStrLn . show . (+1) $ 1
seria equivalente. Você está certo de que a maioria dos operadores de infixos (todos?) São funções.
map ($3)
. Quero dizer, também uso principalmente $
para evitar parênteses, mas não é para isso que eles servem .
map ($3)
é uma função do tipo Num a => [(a->b)] -> [b]
. Ele pega uma lista de funções que recebem um número, aplica 3 a todas elas e coleta os resultados.
Eles têm diferentes tipos e definições diferentes:
infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)
infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x
($)
destina-se a substituir o aplicativo de função normal, mas com uma precedência diferente para ajudar a evitar parênteses. (.)
é para compor duas funções juntas para criar uma nova função.
Em alguns casos, eles são intercambiáveis, mas isso não é verdade em geral. O exemplo típico de localização é:
f $ g $ h $ x
==>
f . g . h $ x
Em outras palavras, em uma cadeia de $
s, todos, exceto o final, podem ser substituídos por.
x
fosse uma função? Você pode usar .
como final?
x
neste contexto, sim - mas então o "final" seria aplicado a algo diferente de x
. Se você não está inscrevendo-se x
, não é diferente x
ser um valor.
Observe também que ($)
é a função de identidade especializada em tipos de função . A função de identidade é assim:
id :: a -> a
id x = x
Enquanto se ($)
parece com isso:
($) :: (a -> b) -> (a -> b)
($) = id
Observe que eu adicionei intencionalmente parênteses extras na assinatura de tipo.
($)
Geralmente, os usos de podem ser eliminados adicionando parênteses (a menos que o operador seja usado em uma seção). Por exemplo: f $ g x
torna - se f (g x)
.
Os usos de (.)
geralmente são um pouco mais difíceis de substituir; eles geralmente precisam de um lambda ou a introdução de um parâmetro de função explícito. Por exemplo:
f = g . h
torna-se
f x = (g . h) x
torna-se
f x = g (h x)
Espero que isto ajude!
($)
permite que as funções sejam encadeadas sem adicionar parênteses para controlar a ordem de avaliação:
Prelude> head (tail "asdf")
's'
Prelude> head $ tail "asdf"
's'
O operador de composição (.)
cria uma nova função sem especificar os argumentos:
Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'
Prelude> let second = head . tail
Prelude> second "asdf"
's'
O exemplo acima é sem dúvida ilustrativo, mas realmente não mostra a conveniência de usar a composição. Aqui está outra analogia:
Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"
Se usarmos o terceiro apenas uma vez, podemos evitar nomeá-lo usando um lambda:
Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"
Por fim, a composição permite evitar o lambda:
Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"
Um aplicativo que é útil e demorei algum tempo para descobrir a partir da descrição muito curta em aprender um haskell : Desde:
f $ x = f x
e o parêntese do lado direito de uma expressão que contém um operador infix a converte em uma função de prefixo, com a qual se pode escrever ($ 3) (4+)
análogo (++", world") "hello"
.
Por que alguém faria isso? Para listas de funções, por exemplo. Ambos:
map (++", world") ["hello","goodbye"]`
e:
map ($ 3) [(4+),(3*)]
são mais curtos que map (\x -> x ++ ", world") ...
ou map (\f -> f 3) ...
. Obviamente, as últimas variantes seriam mais legíveis para a maioria das pessoas.
$3
sem o espaço. Se o Template Haskell estiver ativado, isso será analisado como uma emenda, enquanto $ 3
sempre significa o que você disse. Em geral, parece haver uma tendência em Haskell de "roubar" pedaços de sintaxe, insistindo que certos operadores tenham espaços ao seu redor para serem tratados como tal.
Haskell: diferença entre
.
(ponto) e$
(cifrão)Qual é a diferença entre o ponto
(.)
e o cifrão($)
? Pelo que entendi, ambos são açúcares sintáticos por não precisarem usar parênteses.
Eles não são açúcar sintático por não precisarem usar parênteses - são funções - infixadas, portanto podemos chamá-los de operadores.
(.)
e quando usá-lo.(.)
é a função de composição. assim
result = (f . g) x
é o mesmo que construir uma função que transmite o resultado de seu argumento passado g
para f
.
h = \x -> f (g x)
result = h x
Use (.)
quando você não tiver os argumentos disponíveis para passar para as funções que deseja compor.
($)
e quando usá-la($)
é uma função de associação associativa correta com baixa precedência de ligação. Portanto, apenas calcula as coisas à direita primeiro. Portanto,
result = f $ g x
é o mesmo, proceduralmente (o que importa desde que Haskell seja avaliado preguiçosamente, ele começará a avaliar f
primeiro):
h = f
g_x = g x
result = h g_x
ou mais concisamente:
result = f (g x)
Use ($)
quando você tiver todas as variáveis para avaliar antes de aplicar a função anterior ao resultado.
Podemos ver isso lendo a fonte de cada função.
Aqui está a fonte para (.)
:
-- | 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)
E aqui está a fonte para ($)
:
-- | 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
Use a composição quando não precisar avaliar imediatamente a função. Talvez você queira passar a função resultante da composição para outra função.
Use o aplicativo quando estiver fornecendo todos os argumentos para uma avaliação completa.
Portanto, para o nosso exemplo, seria semanticamente preferível fazer
f $ g x
quando temos x
(ou melhor, g
argumentos de) e fazemos:
f . g
quando nós não.
... ou você poderia evitar os .
e $
construções usando pipelining :
third xs = xs |> tail |> tail |> head
Isso é depois que você adicionou a função auxiliar:
(|>) x y = y x
$
operador de Haskell realmente funciona mais como F # <|
do que |>
, normalmente em haskell você escreveria a função acima dessa maneira: third xs = head $ tail $ tail $ xs
ou talvez até como third = head . tail . tail
, que na sintaxe do estilo F # seria algo como isto:let third = List.head << List.tail << List.tail
Uma ótima maneira de aprender mais sobre qualquer coisa (qualquer função) é lembrar que tudo é uma função! Esse mantra geral ajuda, mas em casos específicos, como operadores, ajuda a lembrar deste pequeno truque:
:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
e
:t ($)
($) :: (a -> b) -> a -> b
Lembre-se de usar :t
liberalmente e envolver seus operadores ()
!
Minha regra é simples (também sou iniciante):
.
se você deseja passar o parâmetro (chame a função) e$
se ainda não houver parâmetro (componha uma função)Isso é
show $ head [1, 2]
mas nunca:
show . head [1, 2]
Eu acho que um pequeno exemplo de onde você usaria .
e não $
ajudaria a esclarecer as coisas.
double x = x * 2
triple x = x * 3
times6 = double . triple
:i times6
times6 :: Num c => c -> c
Observe que times6
é uma função criada a partir da composição da função.
Todas as outras respostas são muito boas. Mas há um importante detalhe de usabilidade sobre como o ghc trata $, que o verificador de tipo ghc permite a instalação com tipos mais altos de classificação / quantificação. Se você olhar para o tipo de, $ id
por exemplo, verá que será necessário uma função cujo argumento é uma função polimórfica. Pequenas coisas assim não recebem a mesma flexibilidade com um operador chateado equivalente. (Isso realmente me faz pensar se $! Merece o mesmo tratamento ou não)