[英]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.