Idris依赖类型的限制

时间:2016-02-20 04:57:18

标签: haskell dependent-type idris

我一直在编写Haskell一段时间,但想尝试使用Idris语言进行一些实验,并依赖打字。我已经玩了一下,并阅读了基本的文档,但是我想表达某种功能,并且不知道如何/如果可能。

以下是我想知道的是否可以表达的几个例子:

first:一个函数,它接受两个自然数,但只检查第一个是否小于另一个。所以f : Nat -> Nat -> whatever其中nat1小于nat2。这个想法是,如果它被称为f 5 10它会起作用,但如果我像f 10 5那样调用它,它将无法键入check。

第二个:一个函数,它接受一个字符串和一个字符串列表,只检查第一个字符串是否在字符串列表中。

在伊德里斯这样的功能是否可行?如果是这样,你会如何实现一个简单的例子?谢谢!

编辑:

在多个用户的帮助下,编写了以下解决方案功能:

module Main

import Data.So

f : (n : Nat) -> (m : Nat) -> {auto isLT : So (n < m)} -> Int
f _ _ = 50

g : (x : String) -> (xs : List String) -> {auto inIt : So (elem x xs)} -> Int
g x xs = 52

main : IO ()
main = putStrLn $ show $ g "hai" ["test", "yo", "ban", "hai", "dog"]

这些当前的解决方案不适用于大型案例。例如,如果你运行数字超过几千的f,它需要永远(不是字面意思)。我认为这是因为类型检查目前是基于搜索的。一位用户评论说,可以通过自己编写证明来自动提供提示。假设这是需要的,那么如何为这些简单案例中的任何一个编写这样的证明?

2 个答案:

答案 0 :(得分:13)

I'm not especially fond of So,或者确实有可避免的证明条款在程序中运行。将您的期望编织到数据本身的结构中会更令人满意。我将写下一个“自然数小于n”的类型。

data Fin : Nat -> Set where
  FZ : Fin (S n)
  FS : Fin n -> Fin (S n)

Fin是一种类似数字的数据类型 - 将FS (FS FZ)的形状与自然数S (S Z)的形状进行比较 - 但还有一些其他类型级结构。为什么叫Finn类型中只有Fin n个唯一成员; Fin因此是有限集的家族。

我的意思是:Fin确实是一种数字。

natToFin : (n : Nat) -> Fin (S n)
natToFin Z = FZ
natToFin (S k) = FS (natToFin k)

finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS i) = S (finToNat i)

重点是:Fin n值必须小于n

two : Fin 3
two = FS (FS FZ)
two' : Fin 4
two' = FS (FS FZ)
badTwo : Fin 2
badTwo = FS (FS FZ)  -- Type mismatch between Fin (S n) (Type of FZ) and Fin 0 (Expected type)

虽然我们在这里,但没有任何数字小于零。也就是说,Fin Z,基数为0的集合是空集。

Uninhabited (Fin Z) where
  uninhabited FZ impossible
  uninhabited (FS _) impossible

如果您的号码小于n,则肯定小于S n。因此,我们有一种方法可以放松Fin

的上限
weaken : Fin n -> Fin (S n)
weaken FZ = FZ
weaken (FS x) = FS (weaken x)

我们也可以采用另一种方式,使用类型检查器自动找到给定Fin上最严格的绑定。

strengthen : (i : Fin n) -> Fin (S (finToNat i))
strengthen FZ = FZ
strengthen (FS x) = FS (strengthen x)

可以安全地定义从较大的Fin个数字中减去Nat个数字。我们还可以表达结果不会比输入更大的事实。

(-) : (n : Nat) -> Fin (S n) -> Fin (S n)
n - FZ = natToFin n
(S n) - (FS m) = weaken (n - m)

这一切都有效,但是有一个问题:weaken通过在O(n)时间重建其参数来工作,我们在-的每次递归调用中应用它,产生O( n ^ 2)减法算法。多么尴尬! weaken只是帮助进行类型检查,但它对代码的渐近时间复杂度产生了极大的影响。我们可以在不削弱递归调用结果的情况下离开吗?

好吧,我们不得不调用weaken,因为每次遇到S时,结果与绑定之间的差异都会增大。我们可以通过轻轻拉下类型来满足它,而不是强行将值推到正确的类型。

(-) : (n : Nat) -> (i : Fin (S n)) -> Fin (S (n `sub` finToNat i))
n - FZ = natToFin n
(S n) - (FS i) = n - i

这种类型的灵感来自于我们成功收紧Finstrengthen的约束。 -结果的界限与它需要的一样严格。

我在该类型中使用的

sub是减去自然数。它截断为零的事实不需要麻烦我们,因为-的类型确保它永远不会发生。 (此功能可在Prelude下以minus的名称找到。)

sub : Nat -> Nat -> Nat
sub n Z = n
sub Z m = Z
sub (S n) (S m) = sub n m

这里要学到的教训就是这个。首先,在我们的数据中构建一些正确性属性导致渐近的减速。我们可以放弃对返回值做出承诺并回到无类型版本,但实际上giving the type checker more information允许我们在不做出牺牲的情况下到达我们的目的地。

答案 1 :(得分:0)

So是很普通的事情,它允许您将任何布尔条件“提升”到类型级别。不过,这种普遍性有其代价,那就是(至少以我的经验)这样的证明更难构建且计算更昂贵。

相反,通常最好自己创建一个特殊的证明类型,该类型仅允许您表达某种类型的条件,但是以一种更简单的方式,可以产生更干净,更容易构造的证明。 Idris标准库中充斥着此类专门的证明类型(或更确切地说,类型族)。果然,它已经包含了您在此关注的内容。

||| Proofs that `n` is less than or equal to `m`
||| @ n the smaller number
||| @ m the larger number
data LTE  : (n, m : Nat) -> Type where
  ||| Zero is the smallest Nat
  LTEZero : LTE Z    right
  ||| If n <= m, then n + 1 <= m + 1
  LTESucc : LTE left right -> LTE (S left) (S right)

(来自Prelude.Nat

类型LTE x y的项表示x不大于y的证明(请注意,它仅适用于Nat s,因为它依赖于该类型的内部结构)。 LTEZero不需要任何参数,因为Z永远都不能大于任何Nat(包括Z本身)。您可以随意构造此类证明。对于其他数字,您可以通过归纳证明LTE的关系(假设LTE x y暗示LTE (S x) (S y)的规则)。通过解构参数,您最终会在其中一个为Z的时刻到达。如果为左,则说明Z小于或等于任何值;如果为右,对不起,您的假设是错误的,因此您将无法构建证明

maybeLTE : (n, m : Nat) -> Maybe (LTE n m)
maybeLTE Z _ = Just LTEZero
maybeLTE _ Z = Nothing
maybeLTE (S n) (S m) = map LTESucc $ maybeLTE n m

请注意,此构造如何不依赖于任何外部订购概念。相反,此类型定义 Nat小于或等于另一个Nat的含义。可以将这两个构造函数(以及类型Nat本身)视为理论的公理,从中可以得出证明。让我们再次看看这些构造函数的类型:

LTEZero : LTE Z right指出Z小于或等于right 对于所有 right s。

LTESucc : LTE left right -> LTE (S left) (S right)表示一个含义:如果left小于或等于right,则S left小于或等于S right

这是库里·霍华德(Curry-Howard)同构的全貌。