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.
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:
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)
non ha un input e quindi non può essere utilizzato con l' .
operatore.show
può prendere un Int
e restituire un String
.putStrLn
può prendere un String
e restituire un IO ()
.È possibile catena show
di putStrLn
come questo:
(putStrLn . show) (1 + 1)
Se sono troppe parentesi per i tuoi gusti, eliminale con l' $
operatore:
putStrLn . show $ 1 + 1
putStrLn . show . (+1) $ 1
sarebbe equivalente. Hai ragione nel dire che la maggior parte (tutti?) Gli operatori infix sono funzioni.
map ($3)
. Voglio dire, lo uso principalmente $
per evitare anche le parentesi, ma non è che sia tutto ciò per cui sono lì.
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.
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.
x
fosse una funzione? Puoi quindi usarlo .
come ultimo?
x
in questo contesto, allora sì - ma poi quello "finale" si applicherebbe a qualcosa di diverso x
. Se non ti stai candidando x
, non è diverso x
dall'essere un valore.
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 x
diventa 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!
($)
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"
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.
$3
senza lo spazio. Se Template Haskell è abilitato, questo verrà analizzato come una giunzione, mentre $ 3
significa 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.
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.
(.)
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 g
al 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.
($)
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 f
primo):
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.
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
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, g
argomenti di), e facciamo:
f . g
quando non lo facciamo.
... 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
$
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 $ xs
o forse anche third = head . tail . tail
, che nella sintassi in stile F # sarebbe qualcosa del genere:let third = List.head << List.tail << List.tail
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 :t
liberamente e avvolgi i tuoi operatori ()
!
La mia regola è semplice (anche io sono principiante):
.
se si desidera passare il parametro (chiamare la funzione) e$
se non ci sono ancora parametri (componi una funzione)Questo è
show $ head [1, 2]
ma mai:
show . head [1, 2]
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.
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 $ id
per 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)