In che modo "concatMap" da mono-traversable è in grado di "estrarre" argomenti comuni?


9

Sto imparando Haskell e stavo facendo un semplice programma seed DB per Yesod quando mi sono imbattuto in questo comportamento che trovo difficile da capire:

testFn :: Int -> Bool -> [Int]
testFn a b = if b then replicate 10 a else []

Sessione GHOD di Yesod:

$ :t concatMap testFn [3]
concatMap testFn [3] :: Bool -> [Int]
$ (concatMap testFn [1,2,3]) True
[1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3]

In qualche modo è stato in grado di "estrarre" quel secondo "Bool" da ciascuna delle mappature in un singolo argomento al curry.

La sessione Preludio GHCI di base standard rifiuta persino di compilare questa espressione:

$ :t concatMap testFn [3]
error:
     Couldn't match type 'Bool -> [Int]' with '[b]'
      Expected type: Int -> [b]
        Actual type: Int -> Bool -> [Int]
     Probable cause: 'testFn' is applied to too few arguments
      In the first argument of 'concatMap', namely 'testFn'
      In the expression: concatMap testFn [3]

Si scopre che Yesod utilizza una libreria mono-attraversabile che ha il suo concatMap:

$ :t concatMap
concatMap
  :: (MonoFoldable mono, Monoid m) =>
     (Element mono -> m) -> mono -> m

Al mio attuale livello di comprensione di Haskell, non sono riuscito a capire come vengano distribuiti i tipi qui. Qualcuno potrebbe spiegarmi (per quanto possibile orientato il principiante) come viene fatto questo trucco? Quale parte di cui testFnsopra è conforme al Element monotipo?

Risposte:


6

Iniziamo elencando alcuni tipi che conosciamo. (Facciamo finta che i numeri siano Intper semplicità - questo non è davvero rilevante.)

testFn :: Int -> Bool -> [Int]
[1,2,3] :: [Int]
True :: Bool

(concatMap testFn [1,2,3]) Trueè uguale a concatMap testFn [1,2,3] True, quindi concatMapdeve avere un tipo corrispondente a tutti quegli argomenti:

concatMap :: (Int -> Bool -> [Int]) -> [Int] -> Bool -> ???

dov'è ???il tipo di risultato. Si noti che, a causa delle regole di associatività, si ->associa a destra, quindi la digitazione sopra è la stessa di:

concatMap :: (Int -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)

Scriviamo il tipo generale sopra quello. Sto aggiungendo alcuni spazi per contrassegnare la somiglianza.

concatMap :: (MonoFoldable mono, Monoid m) =>
             (Element mono -> m              ) -> mono  -> m
concatMap :: (Int          -> (Bool -> [Int])) -> [Int] -> (Bool -> ???)

Ah-ah! Abbiamo una corrispondenza se scegliamo mcome Bool -> [Int]e monocome [Int]. Se lo facciamo, soddisfiamo i vincoli MonoFoldable mono, Monoid m(vedi sotto) e abbiamo anche Element mono ~ Int, quindi ogni tipo di controllo.

Ne deduciamo che ???è [Int]dalla definizione di m.

Informazioni sui vincoli: per MonoFoldable [Int], c'è poco da dire. [Int]è chiaramente un tipo simile a una lista con un Inttipo di elemento, ed è sufficiente per trasformarlo in MonaFoldablecon Intcome suo Element.

Per Monoid (Bool -> [Int]), è un po 'più complesso. Abbiamo che qualsiasi tipo di funzione A -> Bè un monoide se Bè un monoide. Ciò segue eseguendo l'operazione in modo puntuale. Nel nostro caso specifico, contiamo di [Int]essere un monoide e otteniamo:

mempty :: Bool -> [Int]
mempty = \_ -> []

(<>) :: (Bool -> [Int]) -> (Bool -> [Int]) -> (Bool -> [Int])
f <> g = \b -> f b ++ g b

1
Grande spiegazione! Il modo in cui hai spiegato e allineato i tipi è proprio quello che volevo vedere, grazie!
dimsuz
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.