簡體   English   中英

在 Haskell 的 do 塊中使用 let 中的狀態

[英]Using the state in a let in a do block in Haskell

我有以下數據結構和功能:

data BTree a = BLeaf | BNode (BTree a) a (BTree a) deriving (Show, Eq)
freshNodesS :: BTree String -> State [String] (BTree String)
freshNodesS BLeaf = return BLeaf
freshNodesS (BNode l m r) = do  l' <- freshNodesS l
                                let m' = getFresh m s
                                let s' = m : s
                                r' <- freshNodesS r s'
                                return (BNode l' m' r')

有一個問題,我實際上想使用freshNodesS l的狀態,它應該給出(BTree String, [String])的輸出,但在 do 塊中我不能使用(l', s) <- freshNodesS l ,我看到的唯一選擇是將所有內容都放在 lambda 函數中。 但是有沒有辦法我仍然可以使用 do 符號?


在@chi 所說的之后,我這樣做了:

freshNodesS BLeaf           = return BLeaf
freshNodesS (BNode l m r)   = do    l' <- freshNodesS l
                                    m' <- getFreshS m
                                    r' <- freshNodesS r
                                    return (BNode l' m' r')
                    
getFreshS :: String -> State [String] String
getFreshS x = state $ (\s -> let newx = getFresh x s in (newx, newx: s))

那奏效了。

State monad 的全部意義在於自動傳遞狀態,而無需您明確地這樣做。 幾乎沒有函數應該獲取s作為參數,或者返回它。

例如,

let m' = getFresh m s

是可疑的,它可能應該閱讀

m' <- getFresh m

我們會有getFresh :: String -> State [String] String

整個代碼應該讀為

       do l' <- freshNodesS l
          m' <- getFresh m
          r' <- freshNodesS r
          return (BNode l' m' r')

請注意從未提及ss' 這應該看起來像命令式代碼,其中每個函數調用都會修改可變狀態變量,即使代碼沒有明確提及。

現在,在getFresh您將不得不處理狀態,因為沒有辦法解決這個問題。 如果您的State monad 是標准的,您可以使用getput訪問狀態。 你可能需要類似的東西

getFresh :: String -> State [String] String
getFresh m = do
   s <- get          -- read the current state
   let m' = ...      -- compute a fresh name m'
   let s' = m' : s   -- mark m' as used
   put s'            -- write the current state
   return m'

do-notation 只是語法糖重寫<->>=綁定和 lambdas 綁定。 如果你可以用一個來寫,你可以用另一個來寫。 所以,如果你認為你可以用 lambda 來寫這個,我鼓勵你這樣做。 然后,將其重寫為使用 do-notation,您將學到一些東西。 但我懷疑您會遇到同樣的絆腳石,因為正如我所說,使用 lambda 代替 do-notation 實際上沒有什么特別之處。

我很難說你想寫什么,因為你沒有為getFresh提供類型簽名。 有點令人費解的是,這個函數將一個狀態作為直接參數,而不是像程序的其余部分那樣參與 State monad。 我建議重寫它以有簽名

getFresh :: String -> State [String] String
getFresh m = do {...}

您當然必須更改實現,為此我建議您查看getput操作。 但是進行了此更改后,您的freshNodesS函數將不必對狀態參數進行任何手動線程處理,因為它將完全由狀態機制處理,如預期的那樣:

freshNodesS (BNode l m r) = do
  l' <- freshNodesS l
  m' <- getFresh m
  r' <- freshNodesS r
  return (BNode l' m' r')

或者,你可以用 applicative 風格來寫這個:

freshNodesS (BNode l m r) = 
  BNode <$> freshNodesS l <*> getFresh m <*> freshNodesS r

通過這種方式,您可以清楚地表明,除了共享相同的狀態之外,這三個操作中沒有一個相互依賴,並且您不需要為不做太多事情的變量命名。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM