2016-11-10 90 views
18

我用合成一對夫婦的程序(內存)一個成語如下:如何在更新少數時避免引用所有狀態變量?

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 

然而,隨着狀態變量的數量的增加,這種快速獲取方式比必要的更詳細:

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 

正如我想知道的,有沒有辦法只更新幾個狀態變量而不必引用所有未使用的變量呢?我在想像IORef,但State(事實上有一個包stateref),但我不確定是否已經有一些其他人一直使用的常見成語。

+2

我有種感覺,在這種情況下,記錄語法可能更適合。所以像'State MyState()'這裏的'MyState = MyState {a :: Int,b :: String,c :: Bool,...}'。 – trVoldemort

+0

所以如果只更新'a',我可以寫'put $ myState {a = a'}'。 – trVoldemort

+0

您可能也喜歡在新的MyState類型中使用RecordWildCards擴展名 – jberryman

回答

17

這看起來像lenses的工作。尤其是Control.Lens.Tuple模塊.=use在一起:

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

然而,它通常是更好,如果你給的東西在你的國家的專有名詞,例如

{-# LANGUAGE TemplateHaskell #- 

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

這樣的話,你有你的領域更好的名稱:

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

注意,這仍然可以幫助你,如果你不希望使用的鏡頭,因爲你可以使用記錄語法來更新您數據:

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

在這種情況下,也許值得一提的是* lens *中的zoom。 – duplode

11

這是一個很好的情況下,使用記錄,與getsmodify功能操作狀態的子部分:

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變成一個純粹的吸氣函數進入狀態的操作:

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

而且modify變成一個純粹的更新功能進入狀態的操作:

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

lenszoom combinator升降機的計算在一個State monad中轉換成運行在「更大」State monad中的計算。

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

所以,對於一個 「大」 的狀態:

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 

可以 「放大」 狀態的一部分:

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

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

當然,這會工作在大元組以及記錄上,使用lens的內置tuple field accessors

zoom的真實類型簽名比我上面引用的簡單的簽名更普遍。它使用MonadState約束在monad變壓器堆棧下工作,而不是在State中工作。