【问题标题】:Anonymous records: what ways to type-level tag in Haskell?匿名记录:在 Haskell 中有哪些类型级标签的方法?
【发布时间】:2018-09-23 07:46:54
【问题描述】:

我在玩轻量级的匿名唱片,更多的是为他们探索类型理论,而不是任何“工业实力”。我希望对字段进行简单的类型标记。

myRec = (EmpId 54321, EmpName "Jo", EmpPhone "98-7654321")   -- in which

newtype EmpPhone a = EmpPhone a                              -- and maybe
data EmpName a where EmpName :: IsString a => a -> EmpName a -- GADT
data EmpId   a where EmpId   ::             Int -> EmpId Int -- GADT to same pattern

虽然我可以输入newtype EmpId = EmpId Int,但我想对所有标签都遵循相同的模式,这样我就可以去举例:

project (EmpId, EmpName) myRec           -- use tags as field names

我还将使用StandaloneDeriving/DeriveAnyTypederive instance Eq, Show, Num 等。

其他可能的设计

  • 对于记录,我可以使用 HList 或创建自己的数据类型 Tuple0, Tuple1, Tuple2, ...,而不是 Haskell 元组。我认为这不会影响下面的打字问题。
  • 对于标签/字段,我可以将Symbol(类型级别String)作为幻像类型与值配对——例如CTRex 就是这样。然后使用TypeApplications 构建字段。

    data Tag (tag :: Symbol) a = Tag a  
    myRec = (Tag @"EmpId" 54321, ...)
    

这使得字段语法(和投影列表)相当“嘈杂”;还可以防止任何验证 EmpIds 是 Int 等。

三个相关的问题行:

  1. 如何最好地预防

    sillyRec = (EmpId 65432, Just "not my tag", "or [] as constructor",  
                Right "or even worse" :: Either Int String)
    

    我可以声明一个类,只在其中放置我的标签(DeriveAnyClass 还不错),到处放置约束。但是我的标签有一个一致的结构:单个数据构造函数命名与类型相同;单一类型参数,它是数据构造函数的唯一参数。

  2. 如何表达我希望每条记录都遵循一致的类型模式?那就是防止:

    notaRec = (EmpId 76543, EmpName)
    

    EmpName 在投影列表中是可以的,前提是所有其他字段都是裸构造函数。我想说notaRec不好-Kinded,但是裸EmpNameKind * -> *,和*是统一的。所以我的意思更像是:记录中的所有字段都符合相同的类型模式。

  3. 然后当我到达记录集(又名表/关系)时

    myTable = ( myRec,                                -- tuple of tuples  
               (EmpName "Kaz", EmpPhone 987654312, EmpId 87654),  
                EmpId 98765, EmpPhone "21-4365879", EmpName "Bo")
    

    将字段以不同的顺序放置是可以的,因为我们有一个元组。但是EmpPhone 在两条记录中属于两种不同的类型。最后一行根本不是记录:它是“错误”模式的字段。 (与 2 中的裸 EmpName 相同的不匹配。)

    我再次想说这些都是病态的-Kinded。我的字段标签出现在不同的“深度”或不同的类型模式中。

    我想我可以对有效的类型实例/组合进行大量硬编码。有没有更通用的方法?

编辑:回应 cmets。 (是的,我也是凡人。感谢@duplode 弄清楚格式。)

为什么不type Record = (EmpId Int, EmpName String, EmpPhone String)

作为一个类型的同义词很好。但不回答这个问题,因为我希望它等同于这些标签的任何排列。 (我想我可以使用HList 技术验证类型级别的等价性。)

您的目标的某种高级概述 [谢谢大卫]

我想将( ... , ... , ... ) 视为一个集合。因为关系数据库模型说关系是“元组”[不是 Haskell 元组]的集合,而“元组”是标签值对的集合。我还想将project 函数视为具有第一类参数,它是一组标签。 (相比之下,在 Codd 的关系代数中,π 运算符的标签集带有下标,就像运算符的一部分一样。)

这些不可能是 Haskell Sets,因为元素类型不同。我想说元素是一样的Kind;并且相同-Kinded 元素的 Haskell 元组代表 Kind 的集合。但我知道这是在滥用术语。 (我考虑使用Symbol 标签的替代设计可能会更好地显示有Kindiness 方面。)

如果我可以将 Haskell 元组视为 set-ish,我可以使用众所周知的 HList 技术来模拟关系运算符。

如果这有助于解释,我可以用很多样板来做到这一点:

class MyTag a                                 -- type/kind-level predicate

deriving instance MyTag (EmpId Int)           -- uses DeriveAnyClass
                                              -- etc for all my tags
class WellKinded tup
instance WellKinded ()

instance {-# OVERLAPPING #-}
      (MyTag (n1 a1), MyTag (n2 a2), MyTag (n3 a3))
      => WellKinded (n1 a1, n2 a2, n3 a3)     -- and so on for every arity of tuple

instance {-# OVERLAPPABLE #-}
      (MyTag (n1 a1), MyTag (n2 a2), MyTag (n3 a3))
      => WellKinded (a1 -> n1 a1, a2 -> n2 a2, a3 -> n3 a3)

所有这些不同arities的实例很快就会变得乏味,所以我可以转换为HList;在第一个元素的Kind 上发送一个实例;遍历列表以验证所有相同的Kind

对于tuple-of-tuples,检测第一个子元组第一个元素的Kind;遍历和向下。 (再次需要OverlappingInstances:元组的元组仍然是元组。这就是我上面所说的“大量硬编码”的意思。)这似乎并非无法实现。但这确实感觉像是走错了兔子洞。

【问题讨论】:

  • 在有序列表之外添加nbsp;s 可以使代码块正常工作,但它们的间距仍然很奇怪,我不知道如何解决。
  • 问题:您使用匿名记录有什么原因吗?为什么不type Record = (EmpId Int, EmpName String, EmpPhone String)?这可能是微不足道的,但是...
  • @AdamSmith 对于列表项中的代码块,使用八个空格而不是四个(对于列表项中的其他段落,在段落之前添加一个额外的空格)。
  • @duplode 谢谢!这么多年过去了,SO 格式对我来说还是有点神秘......
  • 我基本上不知道你在问什么。我敢打赌 Simon 或 Richard 一眼就能理解(因为这就是他们的思维方式),但我们这里的大多数人都是凡人。您能否尝试对您的目标进行某种高级概述,并从您的具体示例中扩展对您做什么和不想要什么的解释?

标签: haskell record


【解决方案1】:

这太疯狂了,它可能会起作用。拯救模式的同义词:

newtype Label (n :: Symbol) (a :: *) = MkLab a             -- newtype yay!
  deriving (Eq, Ord, Show)

pattern EmpPhone x = MkLab x :: Label "EmpPhone" a
pattern EmpName x  = MkLab x :: IsString a => Label "EmpName" a
pattern EmpId x    = MkLab x :: Label "EmpId" Int

myRec = (EmpId 54321, EmpName "Jo", EmpPhone "98-7654321") -- works a treat

然后回答q的

  1. 要算作一条记录,所有元组元素的类型必须为Label s a

  2. 要算作投影列表,所有元组元素都必须是 a -> Label s a 类型。 (顺便说一句,这行得通。)

  3. 这些是元组作为记录中唯一允许的类型/种类。 因此,要在类型级别解析元组,我只需要发送最左边元素的类型。 我正在寻找类型构造函数Label。 我可以使用HList-style 类型匹配来完成其他所有操作。

对于那些模式,我确实需要开启一些扩展:

{-# LANGUAGE  PatternSynonyms,
KindSignatures, DataKinds,
ScopedTypeVariables,                                       -- for the signatures on patterns
RankNTypes                 #-}                             -- for the signatures with contexts
import GHC.TypeLits                                        -- for the Symbols

【讨论】:

    【解决方案2】:

    这是 2., 3. 的一个答案或至少解释;对 1 的部分回答。

    1. 如何表达我希望每条记录都遵循一致的类型模式?那就是防止:

      notaRec = (EmpId 76543, EmpName)
      

    表面上EmpId 76543匹配类型模式(n a);而EmpName :: a -> (n a)。但是 Hindley-Milner 并没有像那样简单地“匹配”,它使用统一性。所以所有这些都与(n a)统一起来:

                      -- as `(   n        a    )`
    a -> (n a)        -- as `( ((->) a)  (n a) )`
    (b, c)            -- as `( (,) b )    c     `
    (b, c, d)         -- as `( (,,) b c ) d     `        -- etc for all larger Haskell tuples
    [ a ], Maybe a    -- as `( []         a    )`, `( Maybe     a )`
    Either b c        -- as `( (Either b) c    )`
    b -> (Either b c) -- as `( ((->) b)  (Either b c) )` -- for example, bare `Left`
    

    在滥用术语方面不同意我自己的观点:

    我想说这些都是坏人。我的字段标签出现在不同的“深度”...
    但我知道这是在滥用术语。

    具有-> 最外层构造函数的任何类型都位于不同的Kind 与没有的类型。 EitherKindEmpId 不同,因为它的数量不同。类型统一构建了'most g一般unifier',这使得它们看起来相同-Kinded。

    为了这里的目的,我们想要 mgu 的反面——称之为“最大特定Kind”,简称MaSK

    我们可以用一个封闭的类型族和许多重叠的方程来表达它(所以它们的顺序很关键)。这也可以捕获 Prelude 的不应该计算的构造函数:

    type family MaSK ( a :: * ) where
          -- presume the result is one from some pre-declared bunch of types
          -- use that result to verify all 'elements' of a set are same-kinded
    
      MaSK (_  -> _ _ _)       = No            -- e.g. bare `Left`
      MaSK (_  -> [ _ ])       = No            -- reject unwanted constructors
      MaSK (_  -> Maybe _ )    = No            -- ditto
      MaSK (a' -> n a')        = YesAsBareTag  -- this we want
      MaSK (_  -> _ _ )        = No            --
      MaSK (_  -> _ )          = No
    
      MaSK ( _ , _ , _ , _ )   = YesAsSet      -- etc for greater arities
      MaSK ( _ , _ , _ )       = YesAsSet
      MaSK ( _ , _ )           = YesAsSet
    
      MaSK (_ _ _ )            = No            -- too much arity, e.g. `Either b c`
      MaSK  [ _ ]              = No            -- reject unwanted constructors
      MaSK (Maybe _)           = No            -- ditto
    
      MaSK (n a)               = YesAsTagValue -- this we want providing all the above eliminated
      MaSK  _                  = No            -- i.e. bare `Int, Bool, Char, ...`
    

    限制:此方法无法检查该类型是否存在单个数据构造函数,也无法检查该类型的其他构造函数是否与该模式匹配,构造函数的名称是否与该类型相同,也无法检查构造函数是否存在偷运-量化参数。为此,请使用全金属generics

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-10-08
      • 1970-01-01
      • 2010-09-19
      • 2013-01-29
      • 1970-01-01
      • 1970-01-01
      • 2016-02-28
      相关资源
      最近更新 更多