Qual é a diferença entre . (ponto) e $ (cifrão)?

709

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.

Rabarberski
fonte

Respostas:

1226

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 + 1)não possui uma entrada e, portanto, não pode ser usado com o .operador.
  2. showpode pegar um Inte retornar um String.
  3. putStrLnpode pegar um Stringe retornar um IO ().

Você pode encadear showda putStrLnseguinte maneira:

(putStrLn . show) (1 + 1)

Se houver muitos parênteses ao seu gosto, livre-se deles com o $operador:

putStrLn . show $ 1 + 1
Michael Steele
fonte
54
Na verdade, como + também é uma função, você não pode prefixá-lo e compor também, como `putStrLn. exposição . (+) 1 1 `Não que seja mais claro, mas quero dizer ... você poderia, certo?
CodexArcanum 25/10/10
4
@CodexArcanum Neste exemplo, algo como putStrLn . show . (+1) $ 1seria equivalente. Você está certo de que a maioria dos operadores de infixos (todos?) São funções.
Michael Steele
79
Eu me pergunto por que ninguém nunca menciona usos como map ($3). Quero dizer, também uso principalmente $para evitar parênteses, mas não é para isso que eles servem .
cúbico
43
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.
cúbico
21
Você precisa ter cuidado ao usar $ com outros operadores. "x + f (y + z)" não é o mesmo que "x + f $ y + z" porque o último realmente significa "(x + f) (y + z)" (ou seja, a soma de x e f é tratado como uma função).
Paul Johnson
186

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.

GS - Peça desculpas a Monica
fonte
1
E se xfosse uma função? Você pode usar .como final?
richizy
3
@ richizy se você está realmente aplicando xneste 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 xser um valor.
GS - Peça desculpas a Monica
124

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 xtorna - 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!

Martijn
fonte
"Note que eu adicionei intencionalmente parênteses extras na assinatura de tipo." Estou confuso ... por que você fez isso?
Mateen Ulhaq
3
@MateenUlhaq O tipo de ($) é (a -> b) -> a -> b, que é o mesmo que (a -> b) -> (a -> b), mas os parênteses extras adicionam aqui alguns clareza.
Rudi
2
Oh, suponho. Eu estava pensando nisso como uma função de dois argumentos ... mas por causa do currying, é exatamente equivalente a uma função que retorna uma função.
Mateen Ulhaq
78

($) 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"
softmechanics
fonte
3
Se o stackoverflow tivesse uma função de combinação, eu preferiria a resposta combinando as duas explicações anteriores com o exemplo nesta resposta.
precisa saber é o seguinte
59

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.
ellisbben
fonte
29

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.

Christoph
fonte
14
btw, eu desaconselho usar $3sem o espaço. Se o Template Haskell estiver ativado, isso será analisado como uma emenda, enquanto $ 3sempre 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.
GS - Desculpe-se com Monica
1
Demorei um pouco para descobrir como os parênteses estavam funcionando: en.wikibooks.org/wiki/Haskell/…
Casebash
18

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.

Componha, (.)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 gpara 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.

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,

result = f $ g x

é o mesmo, proceduralmente (o que importa desde que Haskell seja avaliado preguiçosamente, ele começará a avaliar fprimeiro):

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.

Leia a fonte

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

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

f $ g x

quando temos x(ou melhor, gargumentos de) e fazemos:

f . g

quando nós não.

Aaron Hall
fonte
12

... 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
user1721780
fonte
2
Sim, |> é o operador de pipeline F #.
user1721780
6
Uma coisa a notar aqui, é que o $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 $ xsou 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
Café elétrico
1
Por que adicionar uma função auxiliar para fazer o Haskell parecer com F #? -1
vikingsteve 22/12/2015
9
O Flipped $já está disponível, e é chamado & hackage.haskell.org/package/base-4.8.0.0/docs/...
pat
11

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 :tliberalmente e envolver seus operadores ()!

ri muito
fonte
11

Minha regra é simples (também sou iniciante):

  • não use .se você deseja passar o parâmetro (chame a função) e
  • não use $se ainda não houver parâmetro (componha uma função)

Isso é

show $ head [1, 2]

mas nunca:

show . head [1, 2]
halacsy
fonte
3
Boa heurística, mas poderia usar mais exemplos
Zoey Hewll
0

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.

Brennan Cheung
fonte
0

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, $ idpor 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)

Carter Tazio Schonwald
fonte