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.

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

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.


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!


"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"

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.

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.


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.


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

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 ()!


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]

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.


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)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.