匿名记录:在Haskell中使用类型级别标记的方法是什么?

时间:2018-04-13 03:08:23

标签: haskell record

我正在玩轻量级的匿名唱片,更多的是探索它们的类型理论而不是任何工业实力'。我希望字段只是类型标记。

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/DeriveAnyType使用derive instance Eq, Show, Num等。

其他可能的设计

  • 对于记录,我可以使用HList或使用我自己的数据类型Tuple0, Tuple1, Tuple2, ...而不是Haskell元组。我认为这不会影响下面的输入问题。
  • 对于代码/字段,我可以将Symbol(类型级String)作为幻像类型与值配对 - 例如CTRex执行类似的操作。然后使用TypeApplications构建字段。

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

这使得字段语法(和投影列表)相当'嘈杂&#39 ;;还会阻止EmpIdInt等的任何验证

有关输入的问题的三个相关行:

  1. 如何最好地预防

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

    我可以声明一个类,只在其中放置我的标签(对DeriveAnyClass来说也不错),在任何地方放置约束。但是我的标签具有一致的结构:单个数据构造函数与类型相同;单一类型参数,它是数据构造函数的唯一参数。

  2. 如何表达我希望每个记录都遵循一致的类型模式?这是预防:

    notaRec = (EmpId 76543, EmpName)
    

    Bare EmpName在投影列表中没问题,提供所有其他字段都是裸构造函数。我想说notaRec不是很好 - Kind,但裸EmpNameKind * -> *,这与*是一致的。所以我的意思更像是:记录中的所有字段都符合相同的类型模式。

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

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

    将字段放在不同的顺序是可以的,因为我们有一个元组元组。但EmpPhone在两个记录中有两种不同的类型。而最后一行根本就没有记录:它的字段是错误的'图案。 (与2中的裸EmpName相同的错误匹配。)

    我想再次说这些病了 - Kind。我的字段标记出现在不同的深度'或者是不同类型的模式。

    我想我可以通过大量硬编码来实现类型的有效实例/组合。有更通用的方法吗?

  4. 编辑:回复评论。 (是的,我也是凡人。感谢@duplode弄清楚格式化。)

      

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

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

      

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

    我想将( ... , ... , ... )视为一组。因为关系数据库模型表明关系是一组元组' [不是Haskell元组]和'元组'是一组标签值。我还想将project函数视为具有第一类参数,该参数是一组标记。 (相比之下,在Codd的关系代数中,π运算符的下标标签就好像是运营商的一部分。)

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

    如果我可以将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)
    

    所有针对不同城市的实例都会很快变得乏味,所以我可以转换为HList;在第一个元素的Kind上发送一个实例;迭代列表,验证所有相同的Kind

    对于tuple-of-tuples,检测第一个子元组的第一个元素的Kind;横向和向下迭代。 (再次需要OverlappingInstances:元组的元组仍然是一个元组。这就是我所说的"大量的硬编码"以上。)它没有&# 39;似乎无法实现。但它确实感觉像走错了兔子洞。

2 个答案:

答案 0 :(得分:0)

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

  
      
  1. 如何表达我希望每个记录都遵循一致的类型模式?这是预防:

    notaRec = (EmpId 76543, EmpName)
    
  2.   

表面上EmpId 76543匹配类型模式(n a);而EmpName :: a -> (n a)。但是欣德利 - 米尔纳并不像这样简单地“匹配”,它使用了统一性。因此,所有这些都与(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`

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

  

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

具有->最外层构造函数的任何类型都与{1}}不同。 Kind处于不同的Either vs Kind,因为它是不同的。类型统一构建' m ost g 通常 u nifier',这使它们显得相同 - EmpId ed。

出于此目的,我们希望与mgu相反 - 将其称为“最大特定Kind”,简称为Kind

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

MaSK

限制:此方法无法检查该类型的单个数据构造函数,也不能检查该类型的其他构造函数是否与模式匹配,也不能将构造函数命名为与类型相同,也不能检查构造函数是否存在于存在性中 - 量化参数。为此,请使用全金属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, ...`

答案 1 :(得分:0)

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

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 - 样式类型匹配来完成所有其他工作。

  4. 对于那些模式我确实需要打开一大堆扩展:

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