简体   繁体   English

用于将一个类型限制为另一个类型的模式

[英]Pattern for constraining a type to another type

I know the title isn't great, so I want to just give a specific example. 我知道标题不是很好,所以我只想举一个具体的例子。 I have a tree-like structure, modeling a test suite: 我有一个树状结构,为测试套件建模:

data Metadata = Metadata { id :: Id, disabled :: Bool }
data Node =
    Suite { metadata :: Metadata, config :: Config, children :: [Node] }
    Test { metadata :: Metadata, code :: string } 

So basically, we have Suite s, which themselves could own other test Suite s, or Test s. 因此,基本上,我们有Suite ,它们本身可以拥有其他测试SuiteTest This is really quite similar to a Tree definition (with parent Tree s and terminal Leaf s). 这确实非常类似于Tree定义(带有父Tree和终端Leaf )。 So far nothing too crazy, except for I've left out other details from the constructors (I used a hypothetical Config type for the Suite constructor, since the details of that aren't pertinent to this question, except to demonstrate that the two constructors are actually significantly different so you wouldn't want a directly recursive structure Node = Node { stuff :: Stuff, children: Maybe [Node] } 到目前为止,没有什么太疯狂的了,除了我从构造函数中遗漏了其他细节(我为Suite构造函数使用了一种假设的Config类型,因为那的细节与这个问题无关,只是为了证明这两个构造函数实际上有很大的不同,因此您不需要直接递归的结构Node = Node { stuff :: Stuff, children: Maybe [Node] }

Now, I have a type that relates to this type. 现在,我有一个与此类型有关的类型。 Specifically, as the tests are running, I keep track of their respective statuses: 具体来说,在测试运行期间,我会跟踪它们各自的状态:

Status =
   Waiting |
   Skipped { dueTo :: Id } |
   Failure { reason :: Reason } |
   Success { duration :: int }

The Test s (and Suite s) could at any given moment be in any one of these states (there are more statuses left out since I do not believe them to be relevant). Test (和Suite )在任何给定时刻都可以处于这些状态中的任何一种状态(因为我认为它们不相关,所以遗漏了更多状态)。 . I store the states in a hash table of sorts from Id to Status : Table Id Status . 我将状态存储在从IdStatus的哈希表中: Table Id Status

The problem arrises that, while Skipped and Waiting are actually totally fine for both Suite and Test , Failure and Success actually want to store slightly different data depending on for which they are. 问题在于,尽管对于SuiteTestSkippedWaiting实际上都很好,但Failure and Success实际上想要根据它们的存储位置略有不同。 Without getting into the weeds of the specific information, say we want the Failure for Suites to store the failed child Id 's to avoid repeatedly re-calculating this (in our case there is actual non-generative data stored). 无需深入了解特定信息,可以说,我们希望“ Failure Suites存储失败的子Id ,以避免重复重新计算(在我们的示例中,存储了实际的非生成性数据)。 One option is to just break it up into two Status es: 一种选择是将其分为两个Status es:

   ...
   TestFailure { reason :: Reason } |
   SuiteFailure { failedChildren :: [Id] } |
   ...

This isn't great though because you need something like isFailure = Status -> Bool since there are TWO failures, and because we can't guarantee that TestFailure s will be associated with Test s in our hash table. 但这不是很好,因为您需要isFailure = Status -> Bool因为有isFailure = Status -> Bool失败,并且因为我们不能保证TestFailure会与哈希表中的Test关联。 We can solve the first problem by splitting out the internal info into a separate type: 我们可以通过将内部信息分成一个单独的类型来解决第一个问题:

data FailureInfo =
   TestInfo { reason :: Reason } |
   SuiteInfo { failedChildren :: [Id], otherStuff :: Whatever }

data Status =
   ...
   Failure { info :: FailureInfo }
   ...

This is certainly better, but there is still the issue that I can't guarantee that a Failure { TestInfo } is only associated with a Test . 这当然更好,但是仍然存在不能保证Failure { TestInfo }仅与Test相关联的问题。 This is the crux of my question: given a type with multiple constructors, how can a variate a supporting type on those constructors in a way that I get the maximum support from the compiler. 这是我的问题的症结所在:给定一个具有多个构造函数的类型,如何才能以一种能够从编译器获得最大支持的方式来改变这些构造函数上的支持类型。

If you imagine temporarily that Suite and Test were actually distinct types (instead of merely constructors of one type), I would maybe want a type parameter Status a , and a hash table mapping from a to Status a (but this would also not completely answer the question). 如果您暂时想象SuiteTest实际上是不同的类型(而不只是一种类型的构造函数),我可能想要类型参数Status a ,以及从a映射到Status a的哈希表(但这也不能完全回答)问题)。

If you have a single hashmap it's going to be difficult to statically guarantee that tests are not mapped to suite-failures or vice versa. 如果您只有一个哈希图,将很难静态地保证测试不会映射到套件失败,反之亦然。 The ideas you already have are pretty much what you can do unless you want to have two different ID-types and require each lookup to specify if a test or suite is being queried for status. 除非您希望拥有两种不同的ID类型并要求每个查询指定是否要查询测试或套件的状态,否则您已经拥有的想法几乎可以执行。

If I may suggest a more drastic change: How about getting rid of the hashtable and storing the tests and statuses in the same structure? 如果我可能建议进行更彻底的更改:摆脱哈希表并将测试和状态存储在同一结构中怎么办?

Something like: 就像是:

-- 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

Depending on how you typically traverse the statuses, this could work quite nicely. 根据您通常如何遍历状态,这可能会很好地工作。 Of course you can still have a HashMap (or two hashmaps) underneath it all if you want (eg Table Id (Status (Either [ID] Reason)) ) and the IO-actions in the tree are just lookups. 当然,如果需要的话,您仍然可以在其下保留一个HashMap(或两个哈希图)(例如, Table Id (Status (Either [ID] Reason)) ,并且树中的IO操作只是查找。 Or you could just have an IORef for each suite/test. 或者您可以为每个套件/测试仅设置一个IORef

Writing or deriving a Functor instance for Node may be very useful. 编写或派生NodeFunctor实例可能非常有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM