【发布时间】: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/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, ...)
这使得字段语法(和投影列表)相当“嘈杂”;还可以防止任何验证 EmpIds 是 Int 等。
三个相关的问题行:
-
如何最好地预防
sillyRec = (EmpId 65432, Just "not my tag", "or [] as constructor", Right "or even worse" :: Either Int String)我可以声明一个类,只在其中放置我的标签(
DeriveAnyClass还不错),到处放置约束。但是我的标签有一个一致的结构:单个数据构造函数命名与类型相同;单一类型参数,它是数据构造函数的唯一参数。 -
如何表达我希望每条记录都遵循一致的类型模式?那就是防止:
notaRec = (EmpId 76543, EmpName)裸
EmpName在投影列表中是可以的,前提是所有其他字段都是裸构造函数。我想说notaRec不好-Kinded,但是裸EmpName是Kind * -> *,和*是统一的。所以我的意思更像是:记录中的所有字段都符合相同的类型模式。 -
然后当我到达记录集(又名表/关系)时
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 一眼就能理解(因为这就是他们的思维方式),但我们这里的大多数人都是凡人。您能否尝试对您的目标进行某种高级概述,并从您的具体示例中扩展对您做什么和不想要什么的解释?