我知道标题不好,所以我只想举一个具体的例子。我有一个树状结构,为测试套件建模:
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
)非常相似。到目前为止,没有什么太疯狂的了,除了我从构造函数中遗漏了其他细节(我对Config
构造函数使用了假设的Suite
类型,因为该细节与该问题无关,除了要证明这两个构造函数实际上存在显着差异之外,因此您不需要直接递归的结构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
的哈希表中。
问题来了,尽管Skipped
和Waiting
实际上对Suite
和Test
都很好,但Failure
和Success
实际上希望根据数据存储的位置略有不同。无需深入了解特定信息,说我们希望Failure
的{{1}}存储失败的孩子Suites
,以避免重复重新计算(在我们的情况下,实际存储的非生成性数据)。一种选择是将其分解为两个Id
:
Status
这不是很好,因为因为有两次失败,您需要使用 ...
TestFailure { reason :: Reason } |
SuiteFailure { failedChildren :: [Id] } |
...
之类的东西,并且因为我们不能保证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
的映射(但这也不能完全回答问题)。
答案 0 :(得分:1)
如果只有一个哈希图,将很难静态地保证测试不会映射到套件失败,反之亦然。 除非您希望拥有两种不同的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
根据您通常如何遍历状态,这可能会很好地工作。当然,如果您愿意(例如Table Id (Status (Either [ID] Reason))
),您仍然可以在其下有一个HashMap(或两个哈希图),并且树中的IO操作只是查找。或者,您可以为每个套件/测试只有一个IORef
。
为Functor
编写或派生一个Node
实例可能非常有用。