Operatore punto in Haskell: bisogno di ulteriori spiegazioni
-
08-07-2019 - |
Domanda
Sto cercando di capire cosa sta facendo l'operatore punto in questo codice Haskell:
sumEuler = sum . (map euler) . mkList
L'intero codice sorgente è sotto.
La mia comprensione
L'operatore punto sta prendendo le due funzioni sum
e il risultato di map euler
e il risultato di mkList
come input.
Ma (map euler)
non è una funzione, è l'argomento della funzione, giusto? Quindi cosa sta succedendo qui?
Inoltre, cosa sta facendo <=>?
Codice
mkList :: Int -> [Int]
mkList n = [1..n-1]
euler :: Int -> Int
euler n = length (filter (relprime n) (mkList n))
sumEuler :: Int -> Int
sumEuler = sum . (map euler) . mkList
Soluzione
In parole povere, .
è composizione delle funzioni, proprio come in matematica:
f (g x) = (f . g) x
Nel tuo caso, stai creando una nuova funzione, sumEuler
che potrebbe anche essere definita in questo modo:
sumEuler x = sum (map euler (mkList x))
Lo stile nel tuo esempio è chiamato " point-free " stile: gli argomenti della funzione vengono omessi. Ciò rende il codice più chiaro in molti casi. (Può essere difficile brontolare la prima volta che lo vedi, ma ti abituerai dopo un po '. È un linguaggio comune di Haskell.)
Se sei ancora confuso, può essere utile correlare f
a qualcosa come una pipe UNIX. Se l'output di g
diventa l'input di h
, il cui output diventa l'input di f < x | g | h
, lo scriverei sulla riga di comando come |
. In Haskell, h . g . f $ x
funziona come UNIX map (\x -> x * 2 + 10) [1..10]
, ma & Quot; indietro & Quot; - (+10) . (*2) <$> [1..10]
. Trovo che questa notazione sia molto utile quando, diciamo, elabora un elenco. Invece di alcune costruzioni ingombranti come (+10) . (*2) $ 10
, potresti semplicemente scrivere <=>. (E, se vuoi applicare quella funzione solo a un singolo valore; è <=>. Coerente!)
La wiki di Haskell ha un buon articolo con qualche dettaglio in più: http://www.haskell.org/ haskellwiki / Pointfree
Altri suggerimenti
Il. l'operatore compone le funzioni. Ad esempio,
a . b
Dove a e b sono funzioni è una nuova funzione che esegue b sui suoi argomenti, quindi a su quei risultati. Il tuo codice
sumEuler = sum . (map euler) . mkList
è esattamente lo stesso di:
sumEuler myArgument = sum (map euler (mkList myArgument))
ma si spera che sia più facile da leggere. La ragione per cui ci sono parentesi attorno a map euler è perché rende più chiaro che ci sono 3 funzioni che sono composte: sum , map euler e mkList - map euler è una singola funzione.
sum
è una funzione nel preludio di Haskell, non un argomento per sumEuler
. Ha il tipo
Num a => [a] -> a
L'operatore di composizione della funzione .
ha tipo
(b -> c) -> (a -> b) -> a -> c
Quindi abbiamo
sum :: Num a => [a] -> a
map :: (a -> b) -> [a] -> [b]
euler :: Int -> Int
mkList :: Int -> [Int]
(map euler) :: [Int] -> [Int]
(map euler) . mkList :: Int -> [Int]
sum . (map euler) . mkList :: Int -> Int
Nota che Int
è un'istanza di Num
.
Il. l'operatore viene utilizzato per la composizione delle funzioni. Proprio come la matematica, se devi funzioni f (x) eg (x) f. g diventa f (g (x)).
map è una funzione integrata che applica una funzione a un elenco. Mettendo la funzione tra parentesi la funzione viene trattata come un argomento. Un termine per questo è currying . Dovresti cercarlo.
Quello che fa è che prende una funzione con diciamo due argomenti, applica l'argomento eulero. (map euler) giusto? e il risultato è una nuova funzione, che accetta solo un argomento.
somma. (mappa eulero). mkList è fondamentalmente un modo elegante di mettere tutto insieme. Devo dire che il mio Haskell è un po 'arrugginito ma forse puoi mettere insieme l'ultima funzione da solo?
L'operatore punto applica la funzione a sinistra (sum
) all'output della funzione a destra. Nel tuo caso, stai concatenando diverse funzioni insieme: stai passando il risultato da mkList
a (map euler)
e quindi passando il risultato a <=>.
Questo sito ha una buona introduzione a molti dei concetti.
Operatore punto in Haskell
Sto cercando di capire cosa sta facendo l'operatore punto in questo codice Haskell:
sumEuler = sum . (map euler) . mkList
Risposta breve
Codice equivalente senza punti, questo è solo
sumEuler = \x -> sum ((map euler) (mkList x))
o senza lambda
sumEuler x = sum ((map euler) (mkList x))
perché il punto (.) indica la composizione della funzione.
Risposta più lunga
Innanzitutto, semplifichiamo l'applicazione parziale da euler
a map
:
map_euler = map euler
sumEuler = sum . map_euler . mkList
Ora abbiamo solo i punti. Cosa è indicato da questi punti?
Da la fonte :
(.) :: (b -> c) -> (a -> b) -> a -> c (.) f g = \x -> f (g x)
Pertanto (.)
è operatore di composizione .
Componi
In matematica, potremmo scrivere la composizione di funzioni, f (x) e g (x), cioè f (g (x)), come
(f & # 8728; g) (x)
che può essere letto " f composto da g " ;.
Quindi in Haskell, f & # 8728; g, o f composto con g, può essere scritto:
f . g
La composizione è associativa, il che significa che f (g (h (x))), scritto con l'operatore di composizione, può tralasciare le parentesi senza alcuna ambiguità.
Cioè, poiché (f & # 8728; g) & # 8728; h è equivalente a f & # 8728; (g & # 8728; h), possiamo semplicemente scrivere f & # 8728; g & # 8728; h.
Tornando indietro
Tornando alla nostra semplificazione precedente, questo:
sumEuler = sum . map_euler . mkList
significa solo che sumEuler
è una composizione non applicata di tali funzioni:
sumEuler = \x -> sum (map_euler (mkList x))