作为对各种依赖类型化形式化技术进行调查的一部分,我遇到了一篇论文,主张使用单例类型(具有一个居民的类型)作为支持依赖类型编程的一种方式。
根据这个来源,在Haskell中,由于诱导类型/值同构,在使用单例类型时,运行时值和编译时类型之间存在分离。
我的问题是:在这方面,单身类型与类型类或引用/具体结构有何不同?
我还特别欣赏关于使用单身人士类型的类型 - 理论重要性/优势的一些直观解释,以及他们一般可以模仿依赖类型的程度。
答案 0 :(得分:27)
正如您所描述的那样,单例类型只有一个值(暂时忽略⊥
)。因此,单例类型的值具有表示该值的唯一类型。依赖型理论(DTT)的关键在于类型可以依赖于值(或者,换句话说,值可以参数化类型)。允许类型依赖于类型的类型理论可以使用单例类型来让类型依赖于单例值。相比之下,类型类提供了 ad hoc多态,其中值可以依赖于类型(相反于DTT,其中类型依赖于值)。
Haskell中一种有用的技术是定义一类相关的单例类型。经典的例子是自然数:
data S n = Succ n
data Z = Zero
class Nat n
instance Nat Z
instance Nat n => Nat (S n)
如果未向Nat
添加更多实例,则Nat
类描述其值/类型是归纳定义的自然数的单例类型。请注意,Zero
是Z
中唯一的居民,但S Int
类型有许多居民(它不是单身人士); Nat
类将S
的参数限制为单例类型。直观地说,任何具有多个数据构造函数的数据类型都不是单例类型。
给出上述内容,我们可以编写依赖类型的后继函数:
succ :: Nat n => n -> (S n)
succ n = Succ n
在类型签名中,类型变量n
可以看作是一个值,因为Nat n
约束将n
限制为表示自然数的单例类型。 succ
然后的返回类型取决于此值,其中S
由值n
参数化。
任何可以归纳定义的值都可以赋予唯一的单例类型表示。
一种有用的技术是使用GADT来参数化具有单例类型(即具有值)的非单例类型。例如,这可以用于给出归纳定义的数据类型的形状的类型级表示。典型的例子是大小清单:
data List n a where
Nil :: List Z a
Cons :: Nat n => a -> List n a -> List (S n) a
这里自然数单例类型按其长度参数列表类型。
根据多态lambda演算,上面的succ
有两个参数,类型n
,然后是类型n
的值参数。因此,这里的单例类型提供了一种Π-type,其中succ :: Πn:Nat . n -> S(n)
其中Haskell中succ
的参数提供了依赖的产品参数n:Nat
(作为类型参数传递)然后是参数值。