2016-11-10 26 views
18

Idiom używać do komponowania kilka procedur (z pamięcią) jest następujący:Jak uniknąć odwoływania się do wszystkich zmiennych stanu podczas aktualizowania tylko kilku?

p1 :: State (Int, String)() 
p1 = do 
    (a, b) <- get 
    ... do something ... 
    put (a', b) 

p2 :: State (Int, String)() 
p2 = do 
    (a, b) <- get 
    ... do something else ... 
    put (a, b') 

main = do 
    ... initializing a0 b0 ... 
    print . flip evalState (a0, b0) 
      . sequence $ replicate 10 p1 ++ repeat p2 

Jednakże, jak liczba zmiennej stanu rośnie, to szybko staje się dużo bardziej gadatliwy niż to konieczne:

p1 :: State (Int, String, Bool, Int, String, Bool)() 
p1 = do 
    (a, b, c, d, e, f) <- get 
    ... do something ... 
    put (a, b, c', d, e, f') 

p2 :: State (Int, String, Bool, Int, String, Bool)() 
p2 = do 
    (a, b, c, d, e, f) <- get 
    ... do something ... 
    put (a', b', c, d, e, f) 

main = do 
    print . flip evalState (a0, b0, c0, d0, e0, f0) 
      . sequence $ replicate 10 p1 ++ repeat p2 

Jak się zastanawiałem, czy istnieje sposób aktualizacji tylko kilku zmiennych stanu bez konieczności odwoływania się do wszystkich nieużywanych? Myślałem coś w stylu IORef, ale dla State (w rzeczywistości jest pakiet stateref), ale nie jestem pewien, czy istnieją już pewne typowe idiomy, których używają inne osoby.

+2

Czuję, że w tym przypadku lepiej pasuje do składni rekordu. Więc coś jak 'State MyState()' gdzie 'dane MyState = MyState {a :: Int, b :: String, c :: Bool, ...}'. – trVoldemort

+0

Więc jeśli tylko 'a' jest aktualizacją, mogę po prostu napisać' put $ myState {a = a '} '. – trVoldemort

+0

możesz również chcieć użyć rozszerzenia 'RecordWildCards' z nowym typem MyState' – jberryman

Odpowiedz

17

Wydaje się to być zadaniem dla lenses. Zwłaszcza moduł Control.Lens.Tuple wraz z .= i use:

p1 = do 
    a <- use _1 
    -- do something -- 
    _1 .= a' 

Jednakże, jest to zwykle lepiej jeśli dać rzeczy w swoim stanie imion, np

{-# LANGUAGE TemplateHaskell #- 

data Record = MkRecord { _age :: Int 
         , _name :: String 
         , _programmer :: Bool 
         } deriving (Show, Eq) 
makeLenses ''Record 

ten sposób masz lepsze nazwy dla swojej dziedzinie:

p1 = do 
    a <- use age 
    -- do something -- 
    age .= a' 

Zauważ, że to nadal pomaga, jeśli nie chcesz używać soczewek, ponieważ można używać składni rekordu zaktualizować swoje dane:

p1 = do 
     r <- get 
     let a = _age r 
     --- do something 
     put $ r{_age = a'} 
+1

. W tym kontekście warto również wspomnieć o 'zoom' z * obiektywu *. – duplode

11

to dobra sytuacja, aby skorzystać rekordy, z gets i modify funkcji do manipulowania w częściach na stanie:

data Env = Env 
    { envNumber :: Int 
    , envText :: String 
    } 

p1 :: State Env() 
p1 = do 
    a <- gets envNumber 
    -- ... 
    modify $ \r -> r { envNumber = a' } 

p2 :: State Env() 
p2 = do 
    b <- gets envText 
    -- ... 
    modify $ \r -> r { envText = b' } 

gets okazuje się czystą funkcję pochłaniacza do działania Stan:

gets :: (s -> a) -> State s a 
envNumber :: Env -> Int 
gets envNumber :: State Env Int 

I modify okazuje się czystą funkcję aktualizacji do działania Stan:

modify :: (s -> s) -> State s() 
(\r -> r { envText = b' }) :: Env -> Env 
modify (\r -> ...) :: State Env() 
4

lens „s zoom combinator podnosi obliczeń w moninie State do obliczeń, które działają w "większej" monadzie State.

zoom :: Lens' s t -> State t a -> State s a 

więc, biorąc pod uwagę "duży" stan:

data Big = Big { 
    _big1 :: Medium, 
    _big2 :: Medium 
} 
data Medium = Medium { 
    _medium1 :: Small, 
    _medium2 :: Small 
} 
data Small = Small { _small :: Int } 

makeLenses ''Big 
makeLenses ''Medium 
makeLenses ''Small 

można "powiększyć" na części stanu:

incr :: State Int() 
incr = id += 1 

incrSmall :: State Big() 
incrSmall = zoom (big2.medium1.small) incr 

Oczywiście, to będzie działać na dużych krotkach i rekordach, przy użyciu wbudowanego lens .

zoom Podpis prawdziwego typu jest bardziej ogólny niż prosty, który cytowałem powyżej. Używa ograniczeń MonadState do pracy pod monadowym stosem transformatora, a nie w szczególności w State.