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.
fonte
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.
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 tipoNum 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:
($)
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 é:
==>
Em outras palavras, em uma cadeia de
$
s, todos, exceto o final, podem ser substituídos por.
fonte
x
fosse uma função? Você pode usar.
como final?x
neste contexto, sim - mas então o "final" seria aplicado a algo diferente dex
. Se você não está inscrevendo-sex
, não é diferentex
ser um valor.Observe também que
($)
é a função de identidade especializada em tipos de função . A função de identidade é assim:Enquanto se
($)
parece com isso: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 - sef (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:torna-se
torna-se
Espero que isto ajude!
fonte
($)
permite que as funções sejam encadeadas sem adicionar parênteses para controlar a ordem de avaliação:O operador de composição
(.)
cria uma nova função sem especificar os argumentos:O exemplo acima é sem dúvida ilustrativo, mas realmente não mostra a conveniência de usar a composição. Aqui está outra analogia:
Se usarmos o terceiro apenas uma vez, podemos evitar nomeá-lo usando um lambda:
Por fim, a composição permite evitar o lambda:
fonte
A versão curta e doce:
($)
chama a função que é seu argumento à esquerda no valor que é seu argumento à direita.(.)
compõe a função que é seu argumento à esquerda na função que é seu argumento à direita.fonte
Um aplicativo que é útil e demorei algum tempo para descobrir a partir da descrição muito curta em aprender um haskell : Desde:
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:
e:
são mais curtos que
map (\x -> x ++ ", world") ...
oumap (\f -> f 3) ...
. Obviamente, as últimas variantes seriam mais legíveis para a maioria das pessoas.fonte
$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.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.
Componha,
(.)
e quando usá-lo.(.)
é a função de composição. assimé o mesmo que construir uma função que transmite o resultado de seu argumento passado
g
paraf
.Use
(.)
quando você não tiver os argumentos disponíveis para passar para as funções que deseja compor.A associação associativa correta se aplica
($)
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,é o mesmo, proceduralmente (o que importa desde que Haskell seja avaliado preguiçosamente, ele começará a avaliar
f
primeiro):ou mais concisamente:
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.
Leia a fonte
Aqui está a fonte para
(.)
:E aqui está a fonte para
($)
:Conclusão
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
quando temos
x
(ou melhor,g
argumentos de) e fazemos:quando nós não.
fonte
... ou você poderia evitar os
.
e$
construções usando pipelining :Isso é depois que você adicionou a função auxiliar:
fonte
$
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é comothird = head . tail . tail
, que na sintaxe do estilo F # seria algo como isto:let third = List.head << List.tail << List.tail
$
já está disponível, e é chamado&
hackage.haskell.org/package/base-4.8.0.0/docs/...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:
e
Lembre-se de usar
:t
liberalmente e envolver seus operadores()
!fonte
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 é
mas nunca:
fonte
Eu acho que um pequeno exemplo de onde você usaria
.
e não$
ajudaria a esclarecer as coisas.Observe que
times6
é uma função criada a partir da composição da função.fonte
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)fonte