众所周知,你可以轻松地从2元组中构建n元组。
record Twople (A B : Set) : Set where
constructor _,_
field
fst : A
snd : B
n-ple : List Set -> Set
n-ple = foldr Twople Unit
(Agda语法,但它可以在Idris中使用,并且可以在Haskell,Scala中使用...)
同样,您可以使用二进制和来构建n元和类型。
data Either (A B : Set) : Set where
left : A -> Either A B
right : B -> Either A B
OneOf : List Set -> Set
OneOf = foldr Either Void
简单,漂亮,但效率低下。从n-ple
中取出最右边的项需要花费O(n)时间,因为你必须在途中解包每个嵌套的Twople
。 n-ple
更像是异类列表而不是元组。同样,在最糟糕的情况下,OneOf
值存在于n-1
right
的末尾。
可以通过手动解压缩数据类型的字段和内联构造函数来缓解低效率:
record Threeple (A B C : Set) : Set where
constructor _,_,_
field
fst : A
snd : B
thd : C
data Threeither (A B C : Set) : Set where
left : A -> Threeither A B C
middle : B -> Threeither A B C
right : C -> Threeither A B C
从Threeple
中挑选项目并对Threeither
执行案例分析现在都是O(1)。但是涉及到很多类型,而不是好的类型 - Fourple
,Nineither
,Hundredple
等等,都必须是单独定义的数据类型。
我必须在O(1)时间和O(1)行代码之间做出选择吗?或者依赖类型可以帮助我吗?一个可以高效地抽象数据类型arity吗?
答案 0 :(得分:6)
对于使用O(1)代码的O(1)字段访问,我们需要数组作为基本对象,或者仍然作为数组实现的某些异构或依赖泛化。阿格达没有这样的事情。
对于n-ary总和,情况稍微有些细微差别,但事情也取决于机器的实现。在这里,O(1)可以合理地表示我们能够使用一个指针解引用和一个构造函数标记检查对任意构造函数进行模式匹配,就像使用本机定义的和类型一样。在Agda,人们可以尝试:
open import Data.Nat
open import Data.Vec
record NSum {n}(ts : Vec Set n) : Set₁ where
field
ix : Fin n
dat : lookup ix ts
当然,这取决于Data.Fin
被实现为机器(大)整数,而AFAIK在当前的Agda中并非如此。我们应该尝试Data.Nat
,因为它有效地实施了:
open import Data.Nat hiding (_<_)
open import Agda.Builtin.Nat using (_<_)
open import Data.Bool
open import Data.List
lookup : ∀ {α}{A : Set α}(as : List A) n → T (n < length as) → A
lookup [] zero ()
lookup [] (suc n) ()
lookup (a ∷ as) zero p = a
lookup (a ∷ as) (suc n) p = lookup as n p
record NSum (ts : List Set) : Set₁ where
constructor nsum
field
ix : ℕ
{bound} : T (ix < length ts)
dat : lookup ts ix bound
foo : NSum (ℕ ∷ Bool ∷ ℕ ∷ Bool ∷ [])
foo = nsum 0 10
bar : NSum (ℕ ∷ Bool ∷ ℕ ∷ Bool ∷ []) → ℕ
bar (nsum 2 dat) = dat
bar _ = 3
请注意,我们使用布尔_<_
而不是默认_<_
,因为涉及前者的证明占用了O(1)空间。此外,lookup
仅在大多数用例的编译时运行,因此它不会破坏我们的O(1)。