Zoeken…


Een monadisch loket

Een voorbeeld van het samenstellen van de lezer, schrijver en staatsmonade met behulp van monadetransformatoren. De broncode is te vinden in deze repository

We willen een teller implementeren, die de waarde verhoogt met een gegeven constante.

We beginnen met het definiëren van enkele typen en functies:

newtype Counter = MkCounter {cValue :: Int}
  deriving (Show)

-- | 'inc c n' increments the counter by 'n' units.
inc :: Counter -> Int -> Counter
inc (MkCounter c) n = MkCounter (c + n)

Stel dat we de volgende berekening willen uitvoeren met behulp van de teller:

  • zet de teller op 0
  • stel de incrementconstante in op 3
  • verhoog de teller 3 keer
  • stel de incrementconstante in op 5
  • verhoog de teller 2 keer

De staatsmonade biedt abstracties voor het doorgeven van de staat. We kunnen gebruik maken van de statusmonade en onze incrementfunctie definiëren als een staatstransformator.

-- | CounterS is a monad.
type CounterS = State Counter

-- | Increment the counter by 'n' units.
incS :: Int-> CounterS ()
incS n = modify (\c -> inc c n)

Dit stelt ons al in staat om een berekening op een meer duidelijke en beknopte manier uit te drukken:

-- | The computation we want to run, with the state monad.
mComputationS :: CounterS ()
mComputationS = do
  incS 3
  incS 3
  incS 3
  incS 5
  incS 5

Maar we moeten nog steeds de incrementconstante passeren bij elke aanroep. We willen dit graag vermijden.

Een omgeving toevoegen

De lezer-monade biedt een handige manier om een omgeving rond te leiden. Deze monade wordt gebruikt in functioneel programmeren om uit te voeren wat in de OO-wereld bekend staat als afhankelijkheidsinjectie .

In zijn eenvoudigste versie heeft de lezer-monade twee typen nodig:

  • het type van de waarde zijn gelezen (dwz milieu, r hieronder)

  • de waarde die wordt geretourneerd door de lezer-monade ( a hieronder).

    Reader ra

We moeten echter ook gebruik maken van de staatsmonade. Daarom moeten we de ReaderT transformator gebruiken:

newtype ReaderT r m a :: * -> (* -> *) -> * -> *

Met ReaderT kunnen we onze teller met omgeving en status als volgt definiëren:

type CounterRS = ReaderT Int CounterS

We definiëren een incR functie die de incrementconstante uit de omgeving haalt (met behulp van ask ) en om onze incrementfunctie te definiëren in termen van onze CounterS monade, maken we gebruik van de lift (die behoort tot de klasse van de monadetransformator ).

-- | Increment the counter by the amount of units specified by the environment.
incR :: CounterRS ()
incR = ask >>= lift . incS

Met behulp van de lezer-monade kunnen we onze berekening als volgt definiëren:

-- | The computation we want to run, using reader and state monads.
mComputationRS :: CounterRS ()
mComputationRS = do
  local (const 3) $ do
    incR
    incR
    incR
    local (const 5) $ do
      incR
      incR

De vereisten zijn gewijzigd: we hebben logboekregistratie nodig!

Ga er nu van uit dat we logging aan onze berekening willen toevoegen, zodat we de evolutie van onze teller op tijd kunnen zien.

We hebben ook een monade om deze taak uit te voeren, de schrijversmonade . Net als bij de lezer-monade moeten we, omdat we ze componeren, gebruik maken van de lezer-monade-transformator:

newtype WriterT w m a :: * -> (* -> *) -> * -> *

Hier stelt w het type van de te accumuleren output voor (wat een monoïde moet zijn, waarmee we deze waarde kunnen accumuleren), m is de binnenste monade en a het type van de berekening.

We kunnen dan onze teller met logging, omgeving en status als volgt definiëren:

type CounterWRS = WriterT [Int] CounterRS

En met behulp van lift kunnen we de versie van de incrementfunctie definiëren die de waarde van de teller na elke increment registreert:

incW :: CounterWRS ()
incW = lift incR >> get >>= tell . (:[]) . cValue

Nu kan de berekening die logboekregistratie bevat als volgt worden geschreven:

mComputationWRS :: CounterWRS ()
mComputationWRS = do
  local (const 3) $ do
    incW
    incW
    incW
    local (const 5) $ do
      incW
      incW

Alles in één keer doen

Dit voorbeeld was bedoeld om monadetransformatoren aan het werk te tonen. We kunnen echter hetzelfde effect bereiken door alle aspecten (omgeving, status en logboekregistratie) in één incrementele bewerking samen te stellen.

Om dit te doen maken we gebruik van typebeperkingen:

inc' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()
inc' = ask >>= modify . (flip inc) >> get >>= tell . (:[]) . cValue

Hier komen we tot een oplossing die werkt voor elke monade die voldoet aan de bovenstaande beperkingen. De berekeningsfunctie wordt dus gedefinieerd met type:

mComputation' :: (MonadReader Int m, MonadState Counter m, MonadWriter [Int] m) => m ()

omdat we in zijn lichaam gebruik maken van inc '.

We zouden deze berekening, bijvoorbeeld in de ghci REPL, als volgt kunnen uitvoeren:

runState ( runReaderT ( runWriterT mComputation' ) 15 )  (MkCounter 0)


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow