说我有数据类型
data Interval :: Nat -> Nat -> * where
Go :: Interval m n -> Interval m (S n)
Empty :: SNat n -> Interval n n
这些是半(右)开放间隔。 Nat
是标准的归纳自然,SNat
是相应的单身。
现在我想在给定间隔的长度内得到一个单身Nat
:
intervalLength :: Interval n (Plus len n) -> SNat len
intervalLength Empty = Z
intervalLength (Go i) = S (intervalLength i)
这不起作用,因为Plus
函数不是单射的。我可以将其定义为
intervalLength :: Interval m n -> SNat (Minus n m)
但我认为这需要一些引理(以及其他约束)。此外,我的间隔出现在以前的形状中。
背景:我尝试在Omega中执行此操作,但它无效(omega bug I filed)
这些问题怎么可能被改进的类型导师破解?供给的RHS能否对LHS模式的类型方程提出至关重要的暗示,以便非内射性抵消?
Agda程序员如何解决这些问题?
答案 0 :(得分:13)
这是我的程序版本。我正在使用
{-# LANGUAGE GADTs, DataKinds, KindSignatures, TypeFamilies #-}
我有Nat
及其单身
data Nat = Z | S Nat
data SNat :: Nat -> * where
ZZ :: SNat Z
SS :: SNat n -> SNat (S n)
我的Interval
类型对我来说比较熟悉,因为“后缀”定义为“小于或等于”:“后缀”,因为如果您从数字升级到列表并标记为{{1}使用元素,您将获得列表后缀的定义。
S
这是补充。
data Le :: Nat -> Nat -> * where
Len :: SNat n -> Le n n
Les :: Le m n -> Le m (S n)
现在,您的谜题是计算某些type family Plus (x :: Nat) (y :: Nat) :: Nat
type instance Plus Z y = y
type instance Plus (S x) y = S (Plus x y)
- 值中的Les
构造函数,提取其索引之间差异的单例。而不是假设我们正在使用某些Le
并尝试计算Le n (Plus m n)
,我将编写一个函数来计算任意之间的差异 SNat m
- indices和建立与Le m o
的连接。
这是Plus
的加法定义,提供了单例。
Le
我们可以轻松确定data AddOn :: Nat -> Nat -> * where
AddOn :: SNat n -> SNat m -> AddOn n (Plus m n)
暗示Le
。某些AddOn
上的模式匹配会显示某些AddOn n o
o
为Plus m n
,并将我们所需的单身人士交给我们。
m
更一般地说,我建议制定依赖类型编程问题,尽量减少计划匹配的类型索引中已定义函数的存在。这避免了复杂的统一。 (Epigram用于将这些功能着色为绿色,因此建议“不要触摸绿色粘液!”。)leAddOn :: Le m o -> AddOn m o
leAddOn (Len n) = AddOn n ZZ
leAddOn (Les p) = case leAddOn p of AddOn n m -> AddOn n (SS m)
,事实证明(因为这是{{1}的要点与Le n o
相比,类型的信息量不逊色,但匹配起来相当容易。
更一般地说,设置一个依赖数据类型是一种非常正常的体验,它可以捕获问题的逻辑但是使用起来非常可怕。这并不意味着捕获正确逻辑的所有数据类型都将是非常可怕的,只需要您更加思考定义的人体工程学。让这些定义变得整洁不是很多人在普通的功能编程学习体验中获得的技能,所以期望能够攀登新的学习曲线。
答案 1 :(得分:8)
我在伊德里斯做了一件事。虽然我同意pigworker关于重新解决问题的建议,但是你需要做的就是让你的定义超过Idris类型检查器。首先,单身Nats:
data SNat : Nat -> Set where
ZZ : SNat O
SS : SNat k -> SNat (S k)
然后,间隔的定义:
data Interval : Nat -> Nat -> Set where
Go : Interval m n -> Interval m (S n)
Empty : SNat n -> Interval n n
你想要的intervalLength的定义看起来有点像这样:
intervalLength : Interval n (plus len n) -> SNat len
intervalLength (Empty sn) = ZZ
intervalLength (Go i) = SS (intervalLength i)
但是你会遇到麻烦,因为正如你所说plus
不是单射的。我们可以通过len
明确地进行模式匹配来获得某些地方 - 然后统一可以取得一些进展:
intervalLength : Interval n (plus len n) -> SNat len
intervalLength {len = O} (Empty sn) = ZZ
intervalLength {len = S k} (Go i) = SS (intervalLength i)
这很好,并且超过了类型检查器,但不幸的是它不相信该功能是完全的:
*interval> :total intervalLength
not total as there are missing cases
缺少的案例就是这个:
intervalLength {len = O} (Go i) = ?missing
如果你试试这个,并向REPL询问missing
的类型,你会看到:
missing : (n : Nat) -> (i : Interval (S n) n) -> SNat O
现在,我们知道类型Interval (S n) n
是空的,但唉,类型检查器不是。不知怎的,我们需要写badInterval : Interval (S n) n -> _|_
,然后我们可以说:
intervalLength {len = O} (Go i) = FalseElim (badInterval i)
我将badInterval
的定义作为练习:-)。这并不是特别棘手,但是有点无聊 - 有时很难避免使用这种类型的类型,但实现badInterval
会支持Pigworker建议不这样做!