【问题标题】:Haskell coproducts and data record field restrictionsHaskell 副产品和数据记录字段限制
【发布时间】:2020-04-06 17:46:14
【问题描述】:

假设我们有以下类型定义:

data P = PA | PB

(不管PA还是PB的结构)

然后,我们可以像这样定义一些其他的记录类型(案例1):

data C1 = C1 { field :: P } -- this is fine

但有时您需要定义更受约束的字段类型(案例 2):

data C2 = C2 { field :: PA } -- won't compile because PA is a data constructor

在 Haskell 中解决此类案例最自然、最惯用的方法是什么? 我试图避免过于复杂或样板的解决方案。

【问题讨论】:

    标签: haskell


    【解决方案1】:

    有一种我从未有机会在实践中使用过的技术,即使用使用的构造函数标记类型。值得一试。

    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE DataKinds #-}
    
    data Tag = TA | TB
    
    -- data P = PA | PB
    data P t where
        PA :: P TA
        PB :: P TB
    

    如果你的函数适用于任何构造函数,那么你可以取一个通用的P t,但你也可以约束:

    f :: P t -> Bool
    f PA = True
    f PB = False
    
    g :: P TA -> ()
    g PA = ()
    -- PB case is unnecessary (and impossible to write)
    

    如果你想返回带有不确定标签的东西,那就有点尴尬了。您可以使用存在主义

    data AnyP where
        AnyP :: P t -> AnyP
    
    h :: Bool -> AnyP
    h True = AnyP PA
    h False = AnyP PB
    

    或者你可以使用existentials的“CPS”编码来避免引入新类型:

    h' :: Bool -> (forall t. P t -> z) -> z
    h' True c = c PA
    h' False c = c PB
    

    我认为这也可能是矫枉过正,你只需要抽象一点不同。如有疑问,请尝试添加类型参数。

    【讨论】:

    • 我认为你有一个错字:应该是 PA :: P TA 而不是 PA :: PA TAPB 相同
    • 但在我看来,这有点像同样数量的样板,不是吗?
    • 这是一个非常有趣的标签技术,感谢分享。我同意@FyodorSoikin,从样板的角度来看,它看起来差不多,但我还没有看到。
    【解决方案2】:

    最自然和惯用的方式是让PA(也可能是PB)成为自己的类型:

    data PAtype = ...
    data PBtype = ...
    data P = PA PAtype | PB PBtype
    
    data C1 = C1 { field :: P }
    data C2 = C2 { field :: PAtype }
    

    【讨论】:

    • 感谢您的回答。这就是我现在的做法,并且有很多类似的数据构造函数和名称,我正试图以某种方式修复它们。
    • 那么你应该把它放在你的问题中。诸如“我知道“标准”方式,但有更好的方式吗?这就是我不喜欢标准方式的地方。”
    • 我不确定这是否真的是一种“标准”方式。如果没有其他人提出更短的版本,我当然会接受。
    【解决方案3】:

    为了完成这个并提供完整的解决方案,目前最好的选择是:

    (稍微扩展一下Fyodor的答案):

    
    module Test 
      P, mkPA, mkPB, PAT ( PAT ), PBT ( PBT )
    where
    
    data PAT = PAT
    data PBT = PBT
    data P = PA PAT | PB PBT
    
    -- Auxiliary type class to use constructors of particular data types, 
    -- hide intermediate data constructors (PA, PB) from external modules.
    -- In this example, they seem trivial and redundant, 
    -- but for complex data types they might be really handy
    
    class IsPA a where
      mkPA :: PAT -> a
    
    class IsPB a where
      mkPB :: PBT -> a
    
    instance IsPA P where
      mkPA = PA
    
    instance IsPB P where
      mkPB = PB
    
    instance IsPA PAT where
      mkPA = id
    
    instance IsPB PBT where
      mkPB = id
    
    

    您现在可以将它们用作:

    
    data C1 = C1 { field :: P }
    data C2 = C2 { field :: PAT }
    
    testC1 = C1 { field = mkPA PAT }
    testC2 = C2 { field = mkPA PAT }
    
    

    我还没有找到比这更简单的选择。我希望我只是不知道一些语言扩展来更有效地解决它。

    【讨论】:

      猜你喜欢
      • 2016-06-02
      • 1970-01-01
      • 2019-07-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-28
      • 1970-01-01
      相关资源
      最近更新 更多