数据系列与内射类型系列

时间:2016-09-26 15:46:45

标签: haskell ghc type-families

既然我们有内射类型系列,那么在数据系列上使用数据系列还有剩余的用例吗?

回顾过去有关数据系列的StackOverflow问题,几年前有this question讨论类型系列和数据系列之间的区别,以及this answer关于数据系列的用例。两者都说数据家族的注入力是他们最大的力量。

查看docs on data families,我发现有理由不使用injective type families重写数据系列的所有用途。

例如,假设我有一个数据系列(我已经合并了一些来自文档的示例,试图挤压数据系列的所有功能)

data family G a b
data instance G Int Bool = G11 Int | G12 Bool deriving (Eq)
newtype instance G () a = G21 a
data instance G [a] b where
   G31 :: c -> G [Int] b
   G32 :: G [a] Bool

我不妨将其重写为

type family G a b = g | g -> a b
type instance G Int Bool = G_Int_Bool
type instance G () a = G_Unit_a a
type instance G [a] b = G_lal_b a b

data G_Int_Bool = G11 Int | G12 Bool  deriving (Eq)
newtype G_Unit_a a = G21 a
data G_lal_b a b where
   G31 :: c -> G_lal_b [Int] b
   G32 :: G_lal_b [a] Bool

不言而喻,数据系列的关联实例以相同的方式对应于具有类型系列的关联实例。那么唯一剩下的区别是我们在type-namespace中有更少的东西?

作为后续内容,在类型命名空间中拥有更少的东西有什么好处?我能想到的是,对于在ghci上玩这个的人来说,这将成为调试地狱 - 构造函数的类型似乎都表明构造函数都在一个GADT下......

3 个答案:

答案 0 :(得分:14)

type family T a = r | r -> a
data family D a

一个内射型家庭T满足注入式公理

  

如果T a ~ T ba ~ b

但是数据族满足更强的生成公理

  

如果D a ~ g bD ~ ga ~ b

(如果您愿意:因为D的实例定义了与现有类型不同的新类型。)

事实上,D本身是类型系统中的合法类型,与T之类的类型系列不同,它只能出现在完全饱和的应用程序中,如T a。这意味着

  • D可以是其他类型构造函数的参数,例如MaybeT D。 (MaybeT T是非法的。)

  • 您可以为D定义实例,例如instance Functor D。 (您无法为类型族Functor T定义实例,并且无论如何它都将无法使用,因为例如map :: Functor f => (a -> b) -> f a -> f b的实例选择依赖于f a类型的事实,您可以同时确定fa;为了实现这一目标,f不能允许在类型系列中发生变化,即使是单独的系列也是如此。)

答案 1 :(得分:4)

您错过了另一个细节 - 数据系列会创建新类型。类型族只能引用其他类型。特别是,数据系列的每个实例都声明了新的构造函数。而且非常通用。如果需要newtype语义,可以使用newtype instance创建数据实例。您的实例可以是记录。它可以有多个构造函数。如果你愿意,它甚至可以是GADT。

typedata / newtype关键字之间的区别正是如此。内射型家庭不会给你新的类型,在你需要它的情况下使它们无用。

我知道你来自哪里。最初我有同样的问题。然后我终于遇到了一个用例,即使没有涉及类型类,它们也很有用。

我想编写一个api来处理几个不同上下文中的可变单元格,而不使用类。我知道我想在IOST中使用免费的monad和口译人员来做这件事,也许还有unsafeCoerce的一些可怕的黑客攻击甚至可以将其编成State }。当然,这不是出于任何实际目的 - 我只是在探索API设计。

所以我有这样的事情:

data MutableEnv (s :: k) a ...

newRef :: a -> MutableEnv s (Ref s a)
readRef :: Ref s a -> MutableEnv s a
writeRef :: Ref s a -> a -> MutableEnv s ()

MutableEnv的定义并不重要。只是标准的自由/操作monad东西,构造函数匹配api中的三个函数。

但我被困在什么定义Ref as。我不想要某种类,我希望它就类型系统而言是一种具体的类型。

然后一天晚上我出去散步了,它击中了我 - 我本质上想要的是一个类型,其构造函数由参数类型索引。但它必须是开放的,不像GADT - 可以随意添加新的口译员。然后它就撞到了我。这正是数据系列的特征。一个开放的,类型索引的数据值族。我可以用以下内容完成api:

data family Ref (s :: k) :: * -> *

然后,处理Ref的基础表示没什么大不了的。只要定义了MutableEnv的解释器,就可以创建一个数据实例(或者更有可能是newtype实例)。

这个确切的例子并不是很有用。但它清楚地说明了数据家族可以做的一些内射型家庭可以做的事情。

答案 2 :(得分:3)

answer by Reid Barton完美地解释了我的两个例子之间的区别。它让我想起了我在Richard Eisenberg的thesis中读到的有关向Haskell添加依赖类型的内容,我认为由于这个问题的核心是注入性和生成性,所以值得一提的是DependentHaskell将如何处理有了它(当它最终实现时,如果现在提出的量词是最终实现的那些)。

以下内容基于上述thesis的第56和57页(4.3.4匹配性):

  

定义(生成性) 如果fg是生成性的,则f a ~ g b暗示{{1} }}

     

定义(注意力) 如果f ~ g是单射的,则f暗示f a ~ f b

     

定义(可匹配性) 如果函数a ~ b是生成性和内射性的,则它是匹配的

在我们现在所知的Haskell(8.0.1)中,匹配(类型级)函数完全由newtype,data和data family类型构造函数组成。将来,在f下,我们将获得的新量词之一将是DependentHaskell,这将用于表示可匹配的函数。换句话说,有一种方法可以告知编译器类型级函数是生成的(目前只能通过确保函数是一个类型构造函数来完成)。