Operatore punto in Haskell: servono ulteriori spiegazioni


87

Sto cercando di capire cosa sta facendo l'operatore punto in questo codice Haskell:

sumEuler = sum . (map euler) . mkList

L'intero codice sorgente è di seguito.

La mia comprensione

L'operatore punto prende le due funzioni sume il risultato di map eulere il risultato di mkListcome input.

Ma sumuna funzione non è l'argomento della funzione, giusto? Allora cosa sta succedendo qui?

Inoltre, cosa sta (map euler)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

Risposte:


139

In parole povere, .è la composizione della funzione, proprio come in matematica:

f (g x) = (f . g) x

Nel tuo caso stai creando una nuova funzione, sumEulerche potrebbe anche essere definita così:

sumEuler x = sum (map euler (mkList x))

Lo stile nel tuo esempio è chiamato stile "senza punti": gli argomenti della funzione vengono omessi. Ciò rende il codice più chiaro in molti casi. (Può essere difficile lamentarsi la prima volta che lo vedi, ma ti ci abituerai dopo un po '. È un comune idioma Haskell.)

Se sei ancora confuso, può essere utile relazionarsi .a qualcosa come una pipe UNIX. Se fl'output di diventa gl'input di, il cui output diventa hl'input di, lo scriveresti sulla riga di comando come f < x | g | h. In Haskell, .funziona come UNIX |, ma "al contrario" - h . g . f $ x. Trovo che questa notazione sia molto utile quando, ad esempio, si elabora un elenco. Invece di una costruzione ingombrante come map (\x -> x * 2 + 10) [1..10], potresti semplicemente scrivere (+10) . (*2) <$> [1..10]. (E, se vuoi applicare quella funzione solo a un singolo valore; è (+10) . (*2) $ 10. Coerente!)

Il wiki Haskell ha un buon articolo con qualche dettaglio in più: http://www.haskell.org/haskellwiki/Pointfree


1
Piccolo cavillo: il primo snippet di codice non è effettivamente valido Haskell.
Swifts Namesake

2
@SwiftsNamesake Per quelli di noi che non parlano fluentemente Haskell, vuoi semplicemente dire che il segno di uguale singolo non ha significato qui? (Quindi lo snippet avrebbe dovuto essere formattato " f (g x)= (f . g) x"?) O qualcos'altro?
user234461

1
@ user234461 Esatto, sì. Avresti bisogno ==invece se volessi Haskell standard valido.
SwiftsNamesake

Quel piccolo frammento in alto è solo oro. Come le altre risposte qui sono corrette, ma quello snippet ha fatto clic direttamente intuitivamente nella mia testa, il che ha reso superfluo leggere il resto della tua risposta.
Tarick Welling

24

Il . l'operatore compone le funzioni. Per esempio,

a . b

Dove un e b sono funzioni è una nuova funzione che corre b sui suoi argomenti, poi una su questi risultati. Il tuo codice

sumEuler = sum . (map euler) . mkList

è esattamente lo stesso di:

sumEuler myArgument = sum (map euler (mkList myArgument))

ma si spera più facile da leggere. Il motivo per cui ci sono parentesi intorno a map euler è perché rende più chiaro che ci sono 3 funzioni da comporre: sum , map euler e mkList - map euler è una singola funzione.


24

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

           euler           ::  Int -> Int
       map                 :: (a   -> b  ) -> [a  ] -> [b  ]
      (map euler)          ::                 [Int] -> [Int]
                    mkList ::          Int -> [Int]
      (map euler) . mkList ::          Int ->          [Int]
sum                        :: Num a =>                 [a  ] -> a
sum . (map euler) . mkList ::          Int ->                   Int

Nota che Intè davvero un'istanza della Numtypeclass.


11

Il . operatore viene utilizzato per la composizione della funzione. Proprio come la matematica, se devi usare le funzioni f (x) eg (x) f. g diventa f (g (x)).

map è una funzione incorporata che applica una funzione a un elenco. Mettendo la funzione tra parentesi, la funzione viene trattata come un argomento. Un termine per questo è curry . 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 richiede un solo argomento.

somma. (mappa eulero). mkList è fondamentalmente un modo elegante per mettere tutto insieme. Devo dire che il mio Haskell è un po 'arrugginito ma forse puoi mettere insieme l'ultima funzione da solo?


6

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

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

Per prima cosa, semplifichiamo l'applicazione parziale di eulera map:

map_euler = map euler
sumEuler = sum . map_euler . mkList

Ora abbiamo solo i punti. Cosa è indicato da questi punti?

Dalla fonte :

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

Così (.)è l' operatore di composizione .

Comporre

In matematica, potremmo scrivere la composizione delle funzioni, f (x) e g (x), cioè f (g (x)), come

(f ∘ g) (x)

che si può leggere "f composta con g".

Quindi in Haskell, f ∘ g, of 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 ∘ g) ∘ h è equivalente a f ∘ (g ∘ h), possiamo semplicemente scrivere f ∘ g ∘ h.

Tornando indietro

Tornando alla nostra precedente semplificazione, questo:

sumEuler = sum . map_euler . mkList

significa semplicemente che sumEulerè una composizione non applicata di quelle funzioni:

sumEuler = \x -> sum (map_euler (mkList x))

4

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 di mkLista (map euler), e poi il risultato di quello a sum. Questo sito ha una buona introduzione a molti dei concetti.

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.