Che tipo di conoscenza o formazione è necessaria per qualcuno per scrivere la definizione di foldlM in questo modo? [chiuso]


9

Di recente, sto cercando di utilizzare Haskell in alcuni dei miei sistemi di produzione di casi reali. Il sistema di tipo Haskell mi offre davvero un grande aiuto. Ad esempio, quando ho capito che ho bisogno di una funzione di tipo

f :: (Foldable t, Monad m) => ( a-> b -> m b) -> b -> t a -> m b

In realtà ci sono funzioni come foldM, foldlMe foldrM.

Tuttavia, ciò che mi ha veramente scioccato è la definizione di queste funzioni, come:

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldr f' return xs z0
  where f' x k z = f z x >>= k

quindi la funzione f'deve essere di tipo:

f' :: a -> b -> b

come richiesto da foldr, quindi bdeve essere di tipo *-> m *, quindi l'intera definizione di foldlMpotrebbe avere un senso.

Un altro esempio include le definizioni di liftA2e<*>

(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 id

liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 f x = (<*>) (fmap f x)

Ho provato alcune delle mie soluzioni prima di sbirciare nel codice sorgente. Ma il divario è così grande che non credo che potrei mai trovare questa soluzione, non importa quante righe di codice scriverò in futuro.

Quindi la mia domanda è: che tipo di conoscenza o quale specifico ramo matematico è necessario affinché qualcuno possa ragionare a un livello così fortemente astratto.

So che la teoria delle categorie potrebbe offrire un po 'di aiuto e sto seguendo questa grande lezione da molto tempo e ci sto ancora lavorando.


13
Haskell è una lingua. Ha molte parole e la maggior parte di quelle parole può essere usata in molti modi diversi. Quando stai imparando una nuova lingua, per anni molte frasi e modi di dire non hanno senso. Ma più lo usi, più vedi schemi familiari e le cose che una volta pensavi fossero intimidatorie e avanzate arrivavano in modo del tutto naturale. Rilassare.
luqui,

Risposte:


3

In generale, logica ecc., Immagino. Ma puoi anche impararlo facendolo. :) Con il passare del tempo noti alcuni schemi, raccogli alcuni trucchi.

In questo modo foldrcon un argomento in più. Alcuni lo vedono come pieghevole in funzioni in modo che possano essere combinati attraverso .e id(che a volte sono davvero <=<e return),

foldr g z xs  =  foldr ($) z . map g $ xs
              =  foldr (.) id (map g xs) z
         ~/=  foldr (<=<) return (map g xs) z
{-
  (\f -> f . f) :: (a -> a) -> (a -> a)

  (\f -> f <=< f) :: Monad m => (a -> m a) -> (a -> m a)
                            (still just a type, not a kind)
-}

Alcuni lo trovano più facile da capire in termini più semplici e sintattici, come

foldr g z [a,b,c,...,n] s =
     g a (foldr g z [b,c,...,n]) s

quindi quando gnon è rigoroso nel suo secondo argomento, spuò servire come uno stato passato da sinistra, anche se stiamo piegando a destra, come esempio.


1
Grazie mille, stavo cercando di capire se questa definizione fosse unica e non mi aspettavo l'uso della composizione Kleisli qui. Questa risposta risolve davvero il mio dubbio.
Teodora,

prego. :)
Will Ness,

4

Quindi il modo migliore per capirlo è farlo. Di seguito è riportata un'implementazione foldlMdell'utilizzo foldlanziché foldr. È un buon esercizio, provalo e vieni più tardi alla soluzione che suggerirei. L'esempio spiega tutto il ragionamento che ho fatto per realizzarlo, che potrebbe essere diverso dal tuo, e potrebbe essere un errore perché ho già saputo di usare un accumulatore di funzioni.

Passaggio 1 : proviamo a scrivere foldlMin termini difoldl

-- this doesn't compile because f returning type is (m b) and not just (b) 
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f z0 xs 

-- So let substitute f by some undefined f'
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
  where f' = undefined

-- cool, but f' should use f somehow in order to get the monadic behaviour
foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' z0 xs
  where f' b a = f somethingIDontkNow 

Qui ti rendi conto che f'è puro e dovresti estrarre il risultato di fdigitare match. L'unico modo per "estrarre" un valore monadico è con l' >>=operatore, ma tale operatore deve essere avvolto subito dopo l'uso.

Quindi, in conclusione: ogni volta che finisci con mi piacerebbe scartare completamente questa monade , arrenditi e basta. Non è la strada giusta

Passaggio 2 : proviamo a scrivere foldlMin termini di foldlma prima usando []come pieghevole, dal momento che è facile da abbinare modello (cioè non abbiamo effettivamente bisogno di usare fold)

-- This is not very hard. It is pretty standard recursion schema. :)
foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 []     = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs

Ok, è stato facile. Confrontiamo la definizione con la normale foldldefinizione per gli elenchi

foldlM' :: (Monad m) => (b -> a -> m b) -> b -> [a] -> m b
foldlM' f z0 []     = return z0
foldlM' f z0 (x:xs) = f z0 x >>= \c -> foldlM' f c xs

myfoldl :: (b -> a -> b) -> b -> [a] -> b
myfoldl f z0 []     = z0
myfoldl f z0 (x:xs) = foldl f (f z0 x) xs

Freddo!! sono praticamente uguali. Il caso banale riguarda esattamente la stessa cosa. Il caso ricorsivo è un po 'diverso, vuoi scrivere qualcosa di più simile a: foldlM' f (f z0 x) xs. Ma non viene compilato come nel passaggio 1, quindi potresti pensare OK, non voglio applicare f, solo per contenere un tale calcolo e comporlo >>=. Mi piacerebbe scrivere qualcosa di più come foldlM' f (f z0 x >>=) xs se avesse senso ...

Passaggio 3 Comprendere che ciò che si desidera accumulare è una composizione di funzioni e non un risultato. ( qui probabilmente sono distorto dal fatto che lo sapevo già perché l'hai pubblicato ).

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' initFunc xs
  where initFunc = undefined :: b -> m b
        f'       = undefined :: (b -> m b) -> a -> (b -> m b) -- This type signature can be deduce because f' should be applied to initFunc and a's from t a. 

Dal tipo initFunce dall'uso delle nostre conoscenze dal passaggio 2 (la definizione ricorsiva) possiamo dedurlo initFunc = return. La definizione di f'può essere completata sapendo che f'dovrebbe usare fe >>=.

foldlM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b
foldlM f z0 xs = foldl f' return xs z0
--                        ^^^^^^
--                        |- Initial value
  where f' b a = \bvalue -> b bvalue >>= \bresult -> f bresult a -- this is equivalent to (b >=> \result -> f result a) which captures the sequence behaviour of the implementation
--         ^      ^^^^^^                  ^^^^^^^
--         |      |                       |- This is the result of previous computation
--         |      |- f' should return a function b -> m b. Any time you have to return a function, start writing a lambda  
--         |- This b is the accumulated value and has type b -> m b
-- Following the types you can write this with enough practise

Come puoi vedere, non è così difficile farlo. Ha bisogno di pratica, ma non sono uno sviluppatore di haskell professionale e potrei farlo da solo, è una questione di pratica


1
Non vedo davvero cosa renda più facile capire una piega a sinistra rispetto a una piega a destra. La piega a destra ha molte più probabilità di produrre un risultato utile per strutture infinite e di essere efficiente per Monadistanze tipiche .
dfeuer,

@dfeuer Il punto non è quello di mostrare un esempio più semplice, ma di proporre un esercizio adeguato per l'OP ed esporre un'argomentazione ragionata della soluzione, cercando di dimostrare che non è necessario essere un haskeller super-master per ottenere tale soluzione. Le digressioni sull'efficienza non sono prese in considerazione
lsmor

3

Non hai bisogno di alcuna conoscenza specifica in matematica per scrivere una funzione simile foldM. Sto usando Haskell in produzione già da 4 anni e sto anche lottando per capire questa definizione di foldM. Ma questo è principalmente perché è scritto male. Per favore, non prenderlo come un errore personale se non riesci a capire qualche codice oscuro. Ecco una versione più leggibile difoldlM

foldlM
    :: forall t m a b .
       (Foldable t, Monad m)
    => (b -> a -> m b)  -- ^ Monadic action
    -> b                -- ^ Starting accumulator
    -> t a              -- ^ List of values
    -> m b              -- ^ Computation result inside a monad
foldlM f z xs = (foldr step pure xs) z
  where
    step :: a -> (b -> m b) -> b -> m b
    step cur next acc = do
        result <- f acc cur
        next result

Questa funzione non è ancora la più semplice. Soprattutto perché ha un utilizzo non tipico di foldrdove l'accumulatore intermedio è una funzione. Ma puoi vedere alcuni mays che rendono tale definizione più leggibile:

  1. Commenti sugli argomenti delle funzioni.
  2. Nomi di argomenti migliori (ancora brevi e idiomatici, ma sono almeno più leggibili).
  3. La firma del tipo esplicito della funzione all'interno where(in modo da conoscere la forma degli argomenti).

Dopo aver visto tale funzione, ora puoi eseguire la tecnica del ragionamento equazionale per espandere la definizione passo dopo passo e vedere come funziona. La capacità di inventare tali funzioni deriva dall'esperienza. Non ho forti capacità matematiche e questa funzione non è una tipica funzione di Haskell. Ma più pratica hai, meglio diventa :)

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.