Applicazione delle funzioni: perché $ viene utilizzato qui?
Domanda
Qualche tempo fa, ho chiesto a domanda su $ e ho ottenuto risposte utili - in effetti, ho pensato di aver capito come usarlo.
Sembra che mi sia sbagliato :(
Questo esempio viene mostrato in un tutorial:
instance Monad [] where
xs >>= f = concat . map f $ xs
Non posso per la vita di me vedere perché $ è stato usato lì; ghci non mi sta aiutando, dato che anche i test che faccio lì sembrano mostrare equivalenza con la versione che semplicemente ometterebbe $. Qualcuno può chiarire questo per me?
Soluzione
Il $
viene utilizzato qui perché ha una precedenza inferiore rispetto alla normale applicazione delle funzioni.
Un altro modo di scrivere questo codice è così:
instance Monad [] where
xs >>= f = (concat . map f) xs
L'idea qui è di costruire prima una funzione ( concat. map f
) e quindi applicarla al suo argomento ( xs
). Come mostrato, questo può essere fatto anche semplicemente mettendo la parentesi attorno alla prima parte.
Nota che non è possibile omettere $
nella definizione originale, si verificherà un errore di tipo. Questo perché l'operatore di composizione della funzione (.
) ha una precedenza inferiore rispetto alla normale applicazione di funzione che trasforma effettivamente l'espressione in:
instance Monad [] where
xs >>= f = concat . (map f xs)
Il che non ha senso, perché il secondo argomento per l'operatore di composizione della funzione non è affatto una funzione. Sebbene la seguente definizione abbia un senso:
instance Monad [] where
xs >>= f = concat (map f xs)
Per inciso, questa è anche la definizione che preferirei, perché mi sembra molto più chiara.
Altri suggerimenti
Vorrei spiegare perché IMHO non è qui lo stile usato:
instance Monad [] where
xs >>= f = concat (map f xs)
concat. map f
è un esempio della cosiddetta scrittura in stile pointfree; dove pointfree significa "senza il punto di applicazione". Ricorda che in matematica, nell'espressione y = f (x)
, diciamo che f
è applicato sul punto x
. Nella maggior parte dei casi, puoi effettivamente eseguire un passaggio finale, sostituendo:
f x = something $ x
con
f = something
come f = concat. map f
, e questo è in realtà uno stile inutile.
Ciò che è più chiaro è discutibile, ma lo stile privo di punti fornisce un diverso punto di vista che è anche utile, quindi a volte viene utilizzato anche quando non è esattamente necessario.
EDIT: ho sostituito inutile con inutile e risolto alcuni esempi, dopo il commento di Alasdair, che dovrei ringraziare.
Il motivo per cui $ viene usato qui è dovuto alla firma del tipo di (.):
(.) :: (b -> c) -> (a -> c) -> a -> c
Qui abbiamo
map f :: [a] -> [[b]]
e
concat :: [[b]] -> [b]
Quindi finiamo con
concat . map f :: [a] -> [b]
e il tipo di (.) potrebbero essere scritti come
(.) :: ([[b]] ??- > [b]) - > ([a] - > [[b]]) - > [a] - > [B]
Se dovessimo usare concat. map f xs
, lo vedremmo
map f xs :: [[b]]
E quindi non può essere usato con (.). (il tipo dovrebbe essere (.) :: (a - > b) - > a - > b