Haskell Language
State Monad
Sök…
Introduktion
State sa
som representerar en beräkning som bär och potentiellt modifierar ett tillstånd av typ s
och ger ett resultat av typ a
, men termen "state monad" kan vanligtvis hänvisa till alla monader som bär ett tillstånd. Paketet mtl
och transformers
ger generella implementeringar av statliga monader.
Anmärkningar
Nykomlingar till Haskell skymmer ofta bort från State
monad och behandlar det som ett tabu - som den påstådda fördelen med funktionell programmering är att undvika stat, så förlorar du inte det när du använder State
? En mer nyanserad vy är att:
- Tillstånd kan vara användbart i små, kontrollerade doser ;
-
State
ger möjlighet att kontrollera dosen mycket exakt.
Orsakerna är att om du har action :: State sa
, säger detta att:
-
action
är speciell eftersom den beror på ett tillstånd; - Tillståndet har typ
s
, såaction
kan inte påverkas av något gammalt värde i ditt program - endast etts
eller något värde som kan nås från någras
; -
runState :: State sa -> s -> (a, s)
sätter en "barriär" runt den statliga åtgärden, så att dess effektivitet inte kan observeras utanför denna barriär.
Så detta är en bra uppsättning kriterier för att använda State
i ett särskilt scenario. Du vill se att din kod minimerar räckvidden för tillståndet , både genom att välja en smal typ för s
och genom att sätta runState
så nära "botten" som möjligt, (så att dina handlingar kan påverkas av så få saker som möjlig.
Numrering av noderna på ett träd med en räknare
Vi har en träddatatyp som denna:
data Tree a = Tree a [Tree a] deriving Show
Och vi vill skriva en funktion som tilldelar ett nummer till varje nod i trädet, från en stegvis räknare:
tag :: Tree a -> Tree (a, Int)
Den långa vägen
Först ska vi göra det den långa vägen runt, eftersom det visar State
monadens låg nivå mekanik ganska snyggt.
import Control.Monad.State
-- Function that numbers the nodes of a `Tree`.
tag :: Tree a -> Tree (a, Int)
tag tree =
-- tagStep is where the action happens. This just gets the ball
-- rolling, with `0` as the initial counter value.
evalState (tagStep tree) 0
-- This is one monadic "step" of the calculation. It assumes that
-- it has access to the current counter value implicitly.
tagStep :: Tree a -> State Int (Tree (a, Int))
tagStep (Tree a subtrees) = do
-- The `get :: State s s` action accesses the implicit state
-- parameter of the State monad. Here we bind that value to
-- the variable `counter`.
counter <- get
-- The `put :: s -> State s ()` sets the implicit state parameter
-- of the `State` monad. The next `get` that we execute will see
-- the value of `counter + 1` (assuming no other puts in between).
put (counter + 1)
-- Recurse into the subtrees. `mapM` is a utility function
-- for executing a monadic actions (like `tagStep`) on a list of
-- elements, and producing the list of results. Each execution of
-- `tagStep` will be executed with the counter value that resulted
-- from the previous list element's execution.
subtrees' <- mapM tagStep subtrees
return $ Tree (a, counter) subtrees'
refactoring
Dela ut räknaren i en postincrement-åtgärd
Den bit där vi get
klicka på den aktuella räknaren och sedan put
tingräknare + 1 kan delas upp i en postIncrement
åtgärd, liknande det som många språk i C-stil ger:
postIncrement :: Enum s => State s s
postIncrement = do
result <- get
modify succ
return result
Dela upp trädet till en högre ordning
Trädvandringslogiken kan delas upp i sin egen funktion, så här:
mapTreeM :: Monad m => (a -> m b) -> Tree a -> m (Tree b)
mapTreeM action (Tree a subtrees) = do
a' <- action a
subtrees' <- mapM (mapTreeM action) subtrees
return $ Tree a' subtrees'
Med denna och postIncrement
funktionen kan vi skriva om tagStep
:
tagStep :: Tree a -> State Int (Tree (a, Int))
tagStep = mapTreeM step
where step :: a -> State Int (a, Int)
step a = do
counter <- postIncrement
return (a, counter)
Använd klassen Traversable
mapTreeM
lösningen ovan kan enkelt skrivas om till en instans av klassen Traversable
:
instance Traversable Tree where
traverse action (Tree a subtrees) =
Tree <$> action a <*> traverse action subtrees
Observera att detta kräver att vi använder Applicative
( <*>
operatören) istället för Monad
.
Med det kan vi nu skriva tag
som en proff:
tag :: Traversable t => t a -> t (a, Int)
tag init t = evalState (traverse step t) 0
where step a = do tag <- postIncrement
return (a, tag)
Observera att detta fungerar för alla Traversable
typ, inte bara vår Tree
typ!
Bli av med Traversable
pannplattan
GHC har en DeriveTraversable
förlängning som eliminerar behovet av att skriva instansen ovan:
{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
data Tree a = Tree a [Tree a]
deriving (Show, Functor, Foldable, Traversable)