Qual è la differenza tra . (punto) e $ (simbolo del dollaro)?


709

Qual è la differenza tra il punto (.)e il simbolo del dollaro ($)?

A quanto ho capito, sono entrambi zucchero sintattico per non aver bisogno di usare le parentesi.

Risposte:


1226

L' $operatore è per evitare le parentesi. Tutto ciò che appare dopo avrà la precedenza su tutto ciò che viene prima.

Ad esempio, supponiamo che tu abbia una riga che dice:

putStrLn (show (1 + 1))

Se vuoi sbarazzarti di quelle parentesi, anche una delle seguenti righe farebbe la stessa cosa:

putStrLn (show $ 1 + 1)
putStrLn $ show (1 + 1)
putStrLn $ show $ 1 + 1

Lo scopo principale .dell'operatore non è evitare le parentesi, ma le funzioni a catena. Ti consente di collegare l'output di tutto ciò che appare a destra con l'input di ciò che appare a sinistra. Questo di solito comporta anche meno parentesi, ma funziona in modo diverso.

Tornando allo stesso esempio:

putStrLn (show (1 + 1))
  1. (1 + 1)non ha un input e quindi non può essere utilizzato con l' .operatore.
  2. showpuò prendere un Inte restituire un String.
  3. putStrLnpuò prendere un Stringe restituire un IO ().

È possibile catena showdi putStrLncome questo:

(putStrLn . show) (1 + 1)

Se sono troppe parentesi per i tuoi gusti, eliminale con l' $operatore:

putStrLn . show $ 1 + 1

55
In realtà, dal momento che + è anche una funzione, non è possibile impostarlo come prefisso e quindi comporlo anche come `putStrLn. mostrare . (+) 1 1 `Non che sia più chiaro, ma voglio dire ... potresti, vero?
CodexArcanum,

4
@CodexArcanum In questo esempio, qualcosa del genere putStrLn . show . (+1) $ 1sarebbe equivalente. Hai ragione nel dire che la maggior parte (tutti?) Gli operatori infix sono funzioni.
Michael Steele,

79
Mi chiedo perché nessuno menziona mai usi come map ($3). Voglio dire, lo uso principalmente $per evitare anche le parentesi, ma non è che sia tutto ciò per cui sono lì.
Cubico

43
map ($3)è una funzione di tipo Num a => [(a->b)] -> [b]. Prende un elenco di funzioni che prendono un numero, ne applica 3 a tutte e raccoglie i risultati.
Cubico,

21
Devi stare attento quando usi $ con altri operatori. "x + f (y + z)" non è lo stesso di "x + f $ y + z" perché quest'ultimo in realtà significa "(x + f) (y + z)" (ovvero la somma di x e f è trattata come una funzione).
Paul Johnson,

186

Hanno tipi diversi e definizioni diverse:

infixr 9 .
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(f . g) x = f (g x)

infixr 0 $
($) :: (a -> b) -> a -> b
f $ x = f x

($)è destinato a sostituire l'applicazione delle normali funzioni ma con una precedenza diversa per evitare parentesi. (.)serve per comporre due funzioni insieme per creare una nuova funzione.

In alcuni casi sono intercambiabili, ma questo non è vero in generale. L'esempio tipico in cui si trovano è:

f $ g $ h $ x

==>

f . g . h $ x

In altre parole in una catena di $s, tutto tranne quello finale può essere sostituito da.


1
E se xfosse una funzione? Puoi quindi usarlo .come ultimo?
richizy

3
@richizy se in realtà ti stai candidando xin questo contesto, allora sì - ma poi quello "finale" si applicherebbe a qualcosa di diverso x. Se non ti stai candidando x, non è diverso xdall'essere un valore.
GS - Scusati con Monica il

124

Si noti inoltre che ($)è la funzione di identità specializzata in tipi di funzione . La funzione Identity è simile alla seguente:

id :: a -> a
id x = x

Mentre ($)assomiglia a questo:

($) :: (a -> b) -> (a -> b)
($) = id

Nota che ho aggiunto intenzionalmente parentesi extra nella firma del tipo.

Gli usi di ($)possono di solito essere eliminati aggiungendo parentesi (a meno che l'operatore non sia usato in una sezione). Ad esempio: f $ g xdiventa f (g x).

Gli usi di (.)sono spesso leggermente più difficili da sostituire; di solito hanno bisogno di un lambda o dell'introduzione di un parametro di funzione esplicita. Per esempio:

f = g . h

diventa

f x = (g . h) x

diventa

f x = g (h x)

Spero che sia di aiuto!


"Nota che ho aggiunto intenzionalmente parentesi extra nella firma del tipo." Sono confuso ... perché l'hai fatto?
Mateen Ulhaq,

3
@MateenUlhaq Il tipo di ($) è (a -> b) -> a -> b, che è lo stesso di (a -> b) -> (a -> b), ma le parentesi extra qui aggiungono un po ' chiarezza.
Rudi,

2
Oh, suppongo. Ci pensavo come una funzione di due argomenti ... ma a causa del curry, è esattamente equivalente a una funzione che restituisce una funzione.
Mateen Ulhaq,

78

($) consente di concatenare le funzioni senza aggiungere parentesi per controllare l'ordine di valutazione:

Prelude> head (tail "asdf")
's'

Prelude> head $ tail "asdf"
's'

L'operatore compose (.)crea una nuova funzione senza specificare gli argomenti:

Prelude> let second x = head $ tail x
Prelude> second "asdf"
's'

Prelude> let second = head . tail
Prelude> second "asdf"
's'

L'esempio sopra è probabilmente illustrativo, ma non mostra davvero la comodità di usare la composizione. Ecco un'altra analogia:

Prelude> let third x = head $ tail $ tail x
Prelude> map third ["asdf", "qwer", "1234"]
"de3"

Se usiamo il terzo solo una volta, possiamo evitare di nominarlo usando un lambda:

Prelude> map (\x -> head $ tail $ tail x) ["asdf", "qwer", "1234"]
"de3"

Infine, la composizione ci consente di evitare la lambda:

Prelude> map (head . tail . tail) ["asdf", "qwer", "1234"]
"de3"

3
Se lo stackoverflow avesse una funzione di combinazione, preferirei la risposta combinando le due spiegazioni precedenti con l'esempio in questa risposta.
Chris.Q,

59

La versione breve e dolce:

  • ($) chiama la funzione che è il suo argomento di sinistra sul valore che è il suo argomento di destra.
  • (.) compone la funzione che è il suo argomento di sinistra sulla funzione che è il suo argomento di destra.

29

Un'applicazione che è utile e mi ha impiegato del tempo per capire dalla breve descrizione di imparare un haskell : Dal:

f $ x = f x

e la parentesi del lato destro di un'espressione contenente un operatore infisso lo converte in una funzione prefisso, a cui si può scrivere in modo ($ 3) (4+)analogo (++", world") "hello".

Perché qualcuno dovrebbe farlo? Per elenchi di funzioni, ad esempio. Tutti e due:

map (++", world") ["hello","goodbye"]`

e:

map ($ 3) [(4+),(3*)]

sono più brevi di map (\x -> x ++ ", world") ...o map (\f -> f 3) .... Ovviamente, le ultime varianti sarebbero più leggibili per la maggior parte delle persone.


14
a proposito, sconsiglio di utilizzare $3senza lo spazio. Se Template Haskell è abilitato, questo verrà analizzato come una giunzione, mentre $ 3significa sempre ciò che hai detto. In generale sembra che ci sia una tendenza in Haskell a "rubare" frammenti di sintassi insistendo sul fatto che alcuni operatori hanno spazi attorno a loro per essere trattati come tali.
GS - Scusati con Monica l'

1
Mi ci è
voluto

18

Haskell: differenza tra .(punto) e $(segno del dollaro)

Qual è la differenza tra il punto (.)e il simbolo del dollaro ($)? A quanto ho capito, sono entrambi zucchero sintattico per non aver bisogno di usare le parentesi.

Sono Non zucchero sintattico per non dover usare parentesi - sono funzioni, - infissa, così possiamo chiamarli operatori.

Componi, (.)e quando usarlo.

(.)è la funzione di composizione. Così

result = (f . g) x

è la stessa come la costruzione di una funzione che passa il risultato del suo argomento passato gal f.

h = \x -> f (g x)
result = h x

Utilizzare (.)quando non si dispone degli argomenti disponibili per passare alle funzioni che si desidera comporre.

Si applica il diritto associativo ($)e quando usarlo

($)è una funzione di applicazione associativa di destra con una precedenza di legame bassa. Quindi calcola semplicemente prima le cose a destra. Così,

result = f $ g x

è lo stesso di questo, proceduralmente (ciò che conta dal momento che Haskell viene valutato pigramente, inizierà a valutare per fprimo):

h = f
g_x = g x
result = h g_x

o più concisamente:

result = f (g x)

Utilizzare ($)quando si hanno tutte le variabili da valutare prima di applicare la funzione precedente al risultato.

Possiamo vederlo leggendo la fonte per ciascuna funzione.

Leggi la fonte

Ecco la fonte per (.):

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

Ed ecco la fonte per ($):

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

Conclusione

Utilizzare la composizione quando non è necessario valutare immediatamente la funzione. Forse vuoi passare la funzione che risulta dalla composizione ad un'altra funzione.

Utilizzare l'applicazione quando si forniscono tutti gli argomenti per una valutazione completa.

Quindi, per il nostro esempio, sarebbe preferibile farlo semanticamente

f $ g x

quando abbiamo x(o meglio, gargomenti di), e facciamo:

f . g

quando non lo facciamo.


12

... oppure potresti evitare le .e le $costruzioni usando il pipelining :

third xs = xs |> tail |> tail |> head

Questo dopo aver aggiunto la funzione di supporto:

(|>) x y = y x

2
Sì, |> è l'operatore della pipeline F #.
user1721780

6
Una cosa da notare qui è che l' $operatore di Haskell in realtà funziona più come F # <|di quanto non faccia |>, in genere in haskell scriveresti la funzione sopra in questo modo: third xs = head $ tail $ tail $ xso forse anche third = head . tail . tail, che nella sintassi in stile F # sarebbe qualcosa del genere:let third = List.head << List.tail << List.tail
Caffè elettrico

1
Perché aggiungere una funzione di supporto per rendere Haskell simile a F #? -1
vikingsteve il

9
Il flipped $è già disponibile e si chiama & hackage.haskell.org/package/base-4.8.0.0/docs/…
pat

11

Un ottimo modo per saperne di più su qualsiasi cosa (qualsiasi funzione) è ricordare che tutto è una funzione! Quel mantra generale aiuta, ma in casi specifici come gli operatori, aiuta a ricordare questo piccolo trucco:

:t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

e

:t ($)
($) :: (a -> b) -> a -> b

Ricorda di usare :tliberamente e avvolgi i tuoi operatori ()!


11

La mia regola è semplice (anche io sono principiante):

  • non utilizzare .se si desidera passare il parametro (chiamare la funzione) e
  • non usare $se non ci sono ancora parametri (componi una funzione)

Questo è

show $ head [1, 2]

ma mai:

show . head [1, 2]

3
Buona euristica, ma potrebbe usare più esempi
Zoey Hewll il

0

Penso a un breve esempio di dove useresti .e non $aiuterei a chiarire le cose.

double x = x * 2
triple x = x * 3
times6 = double . triple

:i times6
times6 :: Num c => c -> c

Si noti che times6è una funzione creata dalla composizione della funzione.


0

Tutte le altre risposte sono abbastanza buone. Ma c'è un importante dettaglio di usabilità su come ghc tratta $, che il checker di tipi ghc consente di installare con gradi più alti / tipi quantificati. Se osservi il tipo di $ idper esempio, scoprirai che prenderà una funzione il cui argomento è esso stesso una funzione polimorfica. Piccole cose del genere non hanno la stessa flessibilità con un operatore turbato equivalente. (Questo in realtà mi chiedo se $! Merita lo stesso trattamento o meno)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.