[英]Pattern for constraining a type to another type
我知道標題不是很好,所以我只想舉一個具體的例子。 我有一個樹狀結構,為測試套件建模:
data Metadata = Metadata { id :: Id, disabled :: Bool }
data Node =
Suite { metadata :: Metadata, config :: Config, children :: [Node] }
Test { metadata :: Metadata, code :: string }
因此,基本上,我們有Suite
,它們本身可以擁有其他測試Suite
或Test
。 這確實非常類似於Tree
定義(帶有父Tree
和終端Leaf
)。 到目前為止,沒有什么太瘋狂的了,除了我從構造函數中遺漏了其他細節(我為Suite
構造函數使用了一種假設的Config
類型,因為那的細節與這個問題無關,只是為了證明這兩個構造函數實際上有很大的不同,因此您不需要直接遞歸的結構Node = Node { stuff :: Stuff, children: Maybe [Node] }
現在,我有一個與此類型有關的類型。 具體來說,在測試運行期間,我會跟蹤它們各自的狀態:
Status =
Waiting |
Skipped { dueTo :: Id } |
Failure { reason :: Reason } |
Success { duration :: int }
Test
(和Suite
)在任何給定時刻都可以處於這些狀態中的任何一種狀態(因為我認為它們不相關,所以遺漏了更多狀態)。 。 我將狀態存儲在從Id
到Status
的哈希表中: Table Id Status
。
問題在於,盡管對於Suite
和Test
, Skipped
和Waiting
實際上都很好,但Failure
and Success
實際上想要根據它們的存儲位置略有不同。 無需深入了解特定信息,可以說,我們希望“ Failure
Suites
存儲失敗的子Id
,以避免重復重新計算(在我們的示例中,存儲了實際的非生成性數據)。 一種選擇是將其分為兩個Status
es:
...
TestFailure { reason :: Reason } |
SuiteFailure { failedChildren :: [Id] } |
...
但這不是很好,因為您需要isFailure = Status -> Bool
因為有isFailure = Status -> Bool
失敗,並且因為我們不能保證TestFailure
會與哈希表中的Test
關聯。 我們可以通過將內部信息分成一個單獨的類型來解決第一個問題:
data FailureInfo =
TestInfo { reason :: Reason } |
SuiteInfo { failedChildren :: [Id], otherStuff :: Whatever }
data Status =
...
Failure { info :: FailureInfo }
...
這當然更好,但是仍然存在不能保證Failure { TestInfo }
僅與Test
相關聯的問題。 這是我的問題的症結所在:給定一個具有多個構造函數的類型,如何才能以一種能夠從編譯器獲得最大支持的方式來改變這些構造函數上的支持類型。
如果您暫時想象Suite
和Test
實際上是不同的類型(而不只是一種類型的構造函數),我可能想要類型參數Status a
,以及從a
映射到Status a
的哈希表(但這也不能完全回答)問題)。
如果您只有一個哈希圖,將很難靜態地保證測試不會映射到套件失敗,反之亦然。 除非您希望擁有兩種不同的ID類型並要求每個查詢指定是否要查詢測試或套件的狀態,否則您已經擁有的想法幾乎可以執行。
如果我可能建議進行更徹底的更改:擺脫哈希表並將測試和狀態存儲在同一結構中怎么辦?
就像是:
-- s is suite data, t is test data
data Node s t =
Suite { metadata :: Metadata, children :: [Node s t], suiteStuff :: s }
Test { metadata :: Metadata, testStuff :: t }
Status f =
Waiting |
Skipped { dueTo :: Id } |
Failure { failStuff :: f } |
Success { duration :: int }
-- Like the old node type, containing test cases
type TestTree = Node Config String
-- A status tree contains IO actions for retrieving the current status of tests/testsuites
type StatusTree = Node (IO (Status [Id])) (IO (Status Reason))
-- Running tests
startRunning :: TestTree -> IO StatusTree
根據您通常如何遍歷狀態,這可能會很好地工作。 當然,如果需要的話,您仍然可以在其下保留一個HashMap(或兩個哈希圖)(例如, Table Id (Status (Either [ID] Reason))
,並且樹中的IO操作只是查找。 或者您可以為每個套件/測試僅設置一個IORef
。
編寫或派生Node
的Functor
實例可能非常有用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.