[英]Path dependent types in Haskell
我正在尝试为Haskell中的某个数据库系统设计一个API,我想以这样一种方式对这个数据库的列进行建模,使得不同表的列之间的交互不会混淆。
更确切地说,假设您有一个类型来表示数据库中的表,与某些类型相关联:
type Table a = ...
并且您可以提取表的列以及列的类型:
type Column col = ...
最后,有各种提取器。 例如,如果您的表包含青蛙的描述,则可以使用函数提取包含青蛙重量的列:
extractCol :: Table Frog -> Column Weight
这是一个问题:我想区分列的来源,以便用户不能在表之间进行操作。 例如:
bullfrogTable = undefined :: Table Frog
toadTable = undefined :: Table Frog
bullfrogWeights = extractCol bullfrogTable
toadWeights = extractCol toadTable
-- Or some other columns from the toad table
toadWeights' = extractCol toadTable
-- This should compile
addWeights toadWeights' toadWeights
-- This should trigger a type error
addWeights bullfrogWeights toadWeights
我知道如何在Scala中实现这一点(使用路径依赖类型,参见[1]),我一直在考虑Haskell中的3个选项:
不使用类型,只是在运行时进行检查(当前的解决方案)
TypeInType扩展,用于在Table类型本身上添加幻像类型,并将此额外类型传递给列。 我并不热衷于此,因为这种类型的构造会非常复杂(表是通过复杂的DAG操作生成的),并且在这种情况下编译可能会很慢。
使用类似于ST monad的forall
构造包装操作,但在我的情况下,我希望额外的标记类型实际上逃避构造。
我很高兴有相同的栏目建设一个非常有限的有效作用域(即从列table
和(id table)
不被混合的),我主要关心的API的DSL的感觉,而不是安全。
我目前的解决方案
这是我最终使用RankNTypes做的事情。
我仍然想让用户能够使用他们认为合适的列,而无需进行强大的类型检查,如果他们想要一些更强的类型保证,请选择加入:这是一个数据科学家的DSL,他不知道Haskell方面的强大功能
表仍然按其内容标记:
type Table a = ...
现在,列包含一些额外的引用类型,它们包含的数据类型如下:
type Column ref col = ...
从表到列的预测标记或未标记。 在实践中,这隐藏在类似镜头的DSL背后。
extractCol :: Table Frog -> Column Frog Weight
data TaggedTable ref a = TaggedTable { _ttTable :: Table a }
extractColTagged :: Table ref Frog -> Column ref Weight
withTag :: Table a -> (forall ref. TaggedTable ref a -> b) -> b
withTag tb f = f (TaggedTable tb)
现在我可以编写如下代码:
let doubleToadWeights = withTag toadTable $ \ttoadTable ->
let toadWeights = extractColTagged ttoadTable in
addWeights toadWeights toadWeights
这将无法编译,如所希望的:
let doubleToadWeights =
toadTable `withTag` \ttoads ->
bullfrogTable `withTag` \tbullfrogs ->
let toadWeights = extractColTagged ttoads
bullfrogWeights = extractColTagged tbullfrogs
in addWeights toadWeights bullfrogWeights -- Type error
从DSL的角度来看,我认为它不像Scala那样简单,但类型错误信息是可以理解的,这对我来说至关重要。
Haskell(据我所知)没有路径依赖类型,但你可以通过使用rank 2类型获得一些方法。 例如,ST monad有一个伪类型参数s
,用于防止runST调用之间的泄漏:
runST :: (forall s . ST s a) -> a
在ST动作中你可以有一个STRef:
newSTRef :: a -> ST s (STRef s a)
但是你得到的STRef带有s
类型参数,所以不允许它从runST
逃脱。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.