有效地抽象数据类型arity

时间:2016-05-20 23:16:03

标签: haskell agda dependent-type idris

众所周知,你可以轻松地从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)时间,因为你必须在途中解包每个嵌套的Twoplen-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)。但是涉及到很多类型,而不是好的类型 - FourpleNineitherHundredple等等,都必须是单独定义的数据类型。

我必须在O(1)时间和O(1)行代码之间做出选择吗?或者依赖类型可以帮助我吗?一个可以高效地抽象数据类型arity吗?

1 个答案:

答案 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)。