Одно из возможных решений с использованием индексного типа:
data FooBarEnd : Nat -> Type where
End : FooBarEnd 0
Foo : FooBarEnd 1 -> FooBarEnd 0
Bar : FooBarEnd 0 -> FooBarEnd 1
Пару тестов:
test1 : FooBarEnd 0
test1 = Foo (Bar (Foo (Bar End)))
test2 : FooBarEnd 0
test2 = End
test3 : FooBarEnd 1
test3 = Bar End
Foo (Foo (Bar End))
не является правильным термином ни для FooBarEnd 0
, ни FooBarEnd 1
.
Недостатком этого подхода является необходимость использования индексов 0
и 1
. Я не уверен, что это то решение, которое вы ищете.
Если вы решили разрешить Foo End
в качестве действительного члена, определение может быть изменено в этом:
data FooBarEnd : Nat -> Type where
End : FooBarEnd 1
Foo : {auto prf : n `GTE` 1} -> FooBarEnd n -> FooBarEnd 0
Bar : {auto prf : n `LTE` 1} -> FooBarEnd n -> FooBarEnd 2
Где GTE
и LTE
означают больше или равно и меньше или равны соответственно.
выше позволяет Foo End
, Bar End
и т.д., сохраняя исходное ограничение, и Идрис способен выводя неявные условия доказательства prf
автоматически.
Более неформально, то (унарный) конструктор Foo
ожидает значение типов FooBarEnd 1
или FooBarEnd 2
в качестве аргумента, что означает, что мы только позволили построить Foo End
или Foo (Bar ...)
, потому что мы отделили каждого из конструкторов в свой собственный «подтип» , индексированный натуральным числом. И конструктор Bar
ожидает либо End
(с индексом 1
< = 1) или Foo
(с индексом 0
< = 1).
Разрешено ли 'Foo End'? –
@ AntonTrunov no ... может быть, хотя ... Мне просто интересно, какой общий синтаксис для этого. – MaiaVictor
Добавлена версия 'Foo End' в мой ответ. –