Как лучше всего использовать число во время компиляции и выполнения?

Я только начинаю изучать Идрис и решил, что для начала неплохо было бы реализовать конечные последовательности в виде деревьев на 2-3 пальца. Каждый внутренний узел в дереве необходимо аннотировать во время выполнения с указанием общего количества элементов, хранящихся под ним, для поддержки быстрого разделения и индексации. Этой информацией о размере также необходимо управлять во время компиляции, чтобы (в конечном итоге) доказать, что разделение с соответствующими индексами и сжатие последовательности с другой последовательностью являются общими операциями.

Я могу придумать два способа справиться с этим:

  1. То, что я сейчас делаю, написав крошечную долю от общего необходимого кода: полностью обрабатываю размеры в типах, а затем использую что-то вроде proof {intros; exact s;}, чтобы добраться до них. Я не знаю, какие ужасные последствия для эффективности это может иметь, если таковые имеются. Среди потенциальных возможностей в моей голове: а) Излишнее сохранение размера с каждым листовым узлом. б) Я думаю, что это маловероятно, но было бы очень плохо, если бы он настаивал на вычислении размеров снизу вверх, а не лениво сверху вниз.

  2. Включите явное поле размера в каждый конструктор узла вместе с доказательством того, что число в поле размера соответствует размеру, необходимому для системы типов. Такой подход кажется крайне неудобным. С другой стороны, я должен быть уверен в том, что номера уровня типа и доказательства равенства будут стерты, оставив только одно число на каждый внутренний узел во время выполнения.

Какой из них, если таковой существует, является правильным?

Текущий код

Не стесняйтесь давать советы по стилю и, возможно, объяснять, как встроить код размера. Я мог только понять, что делать в интерактивном режиме, но мне кажется немного странным иметь внизу доказательства таких простых вещей.

data Tree23 : Nat -> Nat -> Type -> Type where
    Elem : a -> Tree23 0 1 a
    Node2 : Lazy (Tree23 d s1 a) -> Lazy (Tree23 d s2 a) ->
            Tree23 (S d) (s1 + s2) a
    Node3 : Lazy (Tree23 d s1 a) -> Lazy (Tree23 d s2 a) -> Lazy (Tree23 d s3 a) ->
              Tree23 (S d) (s1 + s2 + s3) a

size23 : Tree23 d s a -> Nat
size23 t = ?size23RHS

data Digit : Nat -> Nat -> Type -> Type where
  One : Lazy (Tree23 d s a) -> Digit d s a
  Two : Lazy (Tree23 d s1 a) -> Lazy (Tree23 d s2 a) -> Digit d (s1+s2) a
  Three : Lazy (Tree23 d s1 a) -> Lazy (Tree23 d s2 a) ->
          Lazy (Tree23 d s3 a) -> Digit d (s1+s2+s3) a
  Four : Lazy (Tree23 d s1 a) -> Lazy (Tree23 d s2 a) ->
          Lazy (Tree23 d s3 a) -> Lazy (Tree23 d s4 a) -> Digit d (s1+s2+s3+s4) a

sizeDig : Digit d s a -> Nat
sizeDig t = ?sizeDigRHS

data FingerTree : Nat -> Nat -> Type -> Type where
  Empty : FingerTree d 0 a
  Single : Tree23 d s a -> FingerTree d s a
  Deep : Digit d spr a -> Lazy (FingerTree (S d) sm a) -> Digit d ssf a ->
         FingerTree d (spr + sm + ssf) a

data Seq' : Nat -> Type -> Type where
  MkSeq' : FingerTree 0 n a -> Seq' n a

Seq : Type -> Type
Seq a = (n ** Seq' n a)

---------- Proofs ----------

try.sizeDigRHS = proof
  intros
  exact s

try.size23RHS = proof
  intros
  exact s

Редактировать

Другой вариант, который я немного изучил, - это попытаться отделить структуру данных от ее достоверности. Это приводит к следующему:

data Tree23 : Nat -> Type -> Type where
    Elem : a -> Tree23 0 a
    Node2 : Nat -> Lazy (Tree23 d a) -> Lazy (Tree23 d a) ->
            Tree23 (S d) a
    Node3 : Nat -> Lazy (Tree23 d a) -> Lazy (Tree23 d a) -> Lazy (Tree23 d a) ->
              Tree23 (S d) a

size23 : Tree23 d a -> Nat
size23 (Elem x) = 1
size23 (Node2 s _ _) = s
size23 (Node3 s _ _ _) = s

data Valid23 : Tree23 d a -> Type where
  ElemValid : Valid23 (Elem x)
  Node2Valid : Valid23 x -> Valid23 y -> Valid23 (Node2 (size23 x + size23 y) x y)
  Node3Valid : Valid23 x -> Valid23 y -> Valid23 z
    -> Valid23 (Node3 (size23 x + size23 y + size23 z) x y z)

data Digit : Nat -> Type -> Type where
  One : Lazy (Tree23 d a) -> Digit d a
  Two : Lazy (Tree23 d a) -> Lazy (Tree23 d a) -> Digit d a
  Three : Lazy (Tree23 d a) -> Lazy (Tree23 d a) ->
          Lazy (Tree23 d a) -> Digit d a
  Four : Lazy (Tree23 d a) -> Lazy (Tree23 d a) ->
          Lazy (Tree23 d a) -> Lazy (Tree23 d a) -> Digit d a

data ValidDig : Digit d a -> Type where
  OneValid : Valid23 x -> ValidDig (One x)
  TwoValid : Valid23 x -> Valid23 y -> ValidDig (Two x y)
  ThreeValid : Valid23 x -> Valid23 y -> Valid23 z -> ValidDig (Three x y z)
  FourValid : Valid23 x -> Valid23 y -> Valid23 z -> Valid23 w -> ValidDig (Four x y z w)

sizeDig : Digit d a -> Nat
sizeDig (One x) = size23 x
sizeDig (Two x y) = size23 x + size23 y
sizeDig (Three x y z) = size23 x + size23 y + size23 z
sizeDig (Four x y z w) = (size23 x + size23 y) + (size23 z + size23 w)

data FingerTree : Nat -> Type -> Type where
  Empty : FingerTree d a
  Single : Tree23 d a -> FingerTree d a
  Deep : Nat -> Digit d a -> Lazy (FingerTree (S d) a) -> Digit d a ->
         FingerTree d a

sizeFT : FingerTree d a -> Nat
sizeFT Empty = 0
sizeFT (Single x) = size23 x
sizeFT (Deep k x y z) = k

data ValidFT : FingerTree d a -> Type where
  ValidEmpty : ValidFT Empty
  ValidSingle : Valid23 x -> ValidFT (Single x)
  ValidDeep : ValidDig pr -> ValidFT m -> ValidDig sf ->
              ValidFT (Deep (sizeDig pr + sizeFT m + sizeDig sf) pr m sf)

record Seq : Type -> Type where
  MkSeq : FingerTree 0 a -> Seq a

data ValidSeq : Seq a -> Type where
  MkValidSeq : ValidFT t -> ValidSeq (MkSeq t)

Затем каждая функция сопровождается (отдельным) доказательством ее справедливости.

Мне нравится, как этот подход отделяет код от доказательств, но я столкнулся с парой проблем:

  1. В то время как код становится проще, доказательства, кажется, становится все труднее строить. Я полагаю, что большая часть этого, вероятно, связана с тем, что я не знаком с системой.

  2. На самом деле я еще не подошел к моменту, когда я напишу этот код, но все функции индексации, разделения и архивирования должны будут настаивать на получении доказательства действительности своих входных данных. . Кажется немного странным, что одни функции работают только с последовательностями, а другие настаивают на доказательствах, но, возможно, это только я.


person dfeuer    schedule 26.03.2015    source источник


Ответы (1)


Ваша проблема может быть упрощена до такого типа, как

data Steps : Nat -> Type where
  Nil : Steps 0
  Cons : Steps n -> Steps (S n)

и желая написать

size : Steps n -> Nat

Это очень легко сделать, так как неявно выраженные количественно аргументы (в данном случае n) передаются в size как неявные аргументы! Таким образом, указанный выше тип size совпадает с

size : {n : _} -> Steps n -> Nat

что означает, что его можно определить как

size {n} _ = n
person Cactus    schedule 27.03.2015
comment
Спасибо за это. Есть мысли по поводу эффективности? - person dfeuer; 27.03.2015
comment
Ммм ... github.com/idris-lang/ Идрис-dev / wiki / Erasure-by-usage-analysis может быть актуальным? - person Cactus; 27.03.2015
comment
Актуально, да. Похоже, это гарантирует, что если я явно добавлю информацию о размере во внутренние узлы, то это будет единственная информация о размере, которая попадет во время выполнения. Но я не знаю, что это подразумевает в отношении неявного подхода, когда неявные значения остаются неизменными. Может ли он каким-то образом удалить эту информацию с листьев (например, если я использую size23 (Elem x) = 1; size23 {s} _ = s), или я застрял с этим? - person dfeuer; 27.03.2015
comment
Не могли бы вы взглянуть на мой новый третий подход? - person dfeuer; 28.03.2015