我有一個狀態機,其中狀態是使用 sum 型別實作的。在此處發布簡化版本:
data State =
A { value :: Int }
| B { value :: Int }
| C { other :: String }
我的大部分功能都是單子消耗States 并根據型別執行一些操作。類似于(此代碼無法編譯):
f :: State -> m ()
f st= case st of
s@(A | B) -> withValueAction (value s)
C -> return ()
我知道我可以展開建構式,例如:
f :: State -> m ()
f st= case st of
A v -> withValueAction v
B v -> withValueAction v
C _ -> return ()
但這是很多樣板檔案,并且對更改很脆弱。如果我將引數更改為建構式,我需要重寫case .. of我的代碼庫中的所有內容。
那么如何在建構式的子集上進行模式匹配并訪問共享元素呢?
uj5u.com熱心網友回復:
一種慣用的實作方法是使用稍微不同的value函式:
value :: State -> Maybe Int
value (A v) = Just v
value (B v) = Just v
value _ = Nothing
然后你可以使用這樣的模式保護來撰寫你的案例:
f st | Just v <- value st -> withValueAction v
f C{} = return ()
f _ = error "This should never happen"
或者您可以使用視圖模式甚至更多使用模式同義詞來簡化這一點:
{-# LANGUAGE ViewPatterns, PatternSynonyms #-}
pattern V :: Int -> State
pattern V x <- (value -> Just v)
{-# COMPLETE V, C #-}
f (V x) = withValueAction x
f C{} = return ()
uj5u.com熱心網友回復:
@Noughtmare 的回答演示了如何使用視圖模式來獲得正確的“模式匹配語法”。要自動生成value從多個建構式中選擇共享欄位的函式,您可以使用lens,盡管這種方法需要購買整個 Lens 生態系統。后:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import Control.Lens.TH
data State =
A { _value :: Int }
| B { _value :: Int }
| C { _other :: String }
makeLenses ''State
您將有一個value可用于訪問部分共享欄位的遍歷:
f :: (Monad m) => State -> m ()
f st = case st ^? value of
Just v -> withValueAction v
Nothing -> return ()
uj5u.com熱心網友回復:
這是我最后選擇的解決方案。我的兩個主要要求是:
- 建構式上的“或”模式匹配
- 選擇模式匹配共享的欄位子集
正如@Noughtmare 1 所報告的那樣,目前不可能https://github.com/ghc-proposals/ghc-proposals/pull/522。
因為對于我的問題,可變性的來源主要來自建構式中的引數,而不是來自狀態的數量,所以我選擇的解決方案是啟用NamedFieldPuns擴展,所以解決方案類似于:
f :: State -> m ()
f st= case st of
A {value} -> withValueAction value
B {value} -> withValueAction value
C {} -> return ()
它有一些樣板列舉建構式,但至少在建構式引數中沒有。我將看看視圖模式,當可變性的來源來自建構式的數量而不是引數時,它們可能很有用。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/517168.html
標籤:哈斯克尔造型和型
