Любые трюки, чтобы избавиться от шаблона при построении доказательств абсурдного предиката на перечислениях?

Скажем, у меня

data Fruit = Apple | Banana | Grape | Orange | Lemon | {- many others -}

и предикат этого типа,

data WineStock : Fruit -> Type where
    CanonicalWine : WineStock Grape
    CiderIsWineToo : WineStock Apple

который не выполняется для Banana, Orange, Lemon и других.

Это можно сказать, что это определяет WineStock как предикат на Fruit; WineStock Grape истинно (поскольку мы можем построить значение/доказательство этого типа: CanonicalWine), а также WineStock Apple, но WineStock Banana является ложным, поскольку этот тип не заселен никакими значениями/доказательствами.


Затем, как я могу эффективно использовать Not (WineStock Banana), Not (WineStock Lemon) и т.д.? Кажется, что для каждого конструктора Fruit, кроме Grape и Apple, я не могу не кодировать разбиение на случай WineStock, где-то полное impossible s:

instance Uninhabited (WineStock Banana) where
    uninhabited CanonicalWine impossible
    uninhabited CiderIsWineToo impossible

instance Uninhabited (WineStock Lemon) where
    uninhabited CanonicalWine impossible
    uninhabited CiderIsWineToo impossible

instance Uninhabited (WineStock Orange) where
    uninhabited CanonicalWine impossible
    uninhabited CiderIsWineToo impossible

Обратите внимание, что:

  • код повторяется,
  • LoC будет взрываться, когда определение предиката будет расти, получив больше конструкторов. Представьте себе доказательство Not (Sweet Lemon), предполагая, что в определении Fruit есть много сладких альтернатив.

Таким образом, этот способ не совсем кажется удовлетворительным, почти непрактичным.

Есть ли более элегантные подходы?

Ответ 1

@slcv прав: с помощью функции, которая вычисляет, удовлетворяет ли плод свойству или нет, а не строит различные индуктивные предикаты, вы сможете избавиться от этих накладных расходов.

Вот минимальная настройка:

data Is : (p : a -> Bool) -> a -> Type where
  Indeed : p x = True -> Is p x

isnt : {auto eqF : p a = False} -> Is p a -> b
isnt {eqF} (Indeed eqT) = case trans (sym eqF) eqT of
                            Refl impossible

Is p x гарантирует, что свойство p выполняется для элемента x (я использовал индуктивный тип, а не псевдоним типа, так что Idris не раскрывает его в контексте отверстия, его легче читать таким образом).

isnt prf отклоняет фальшивое доказательство prf всякий раз, когда typechecker может генерировать доказательство, что p a = False автоматически (через Refl или предположение в контексте).

После этого вы можете начать определять свои свойства, только перечисляя положительные случаи и добавляя catchall

wineFruit : Fruit -> Bool
wineFruit Grape = True
wineFruit Apple = True
wineFruit _     = False

weaponFruit : Fruit -> Bool
weaponFruit Apple  = True
weaponFruit Orange = True
weaponFruit Lemon  = True
weaponFruit _      = False

Вы можете определить свои исходные предикаты как псевдонимы типов, вызывающие Is с помощью соответствующей функции решения:

WineStock : Fruit -> Type
WineStock = Is wineFruit

И, конечно же, isnt позволяет отклонить невозможные случаи:

dismiss : WineStock Orange -> Void
dismiss = isnt