Допустим, у нас есть функция merge
, которая просто объединяет два списка:
Order : Type -> Type
Order a = a -> a -> Bool
merge : (f : Order a) -> (xs : List a) -> (ys : List a) -> List a
merge f xs [] = xs
merge f [] ys = ys
merge f (x :: xs) (y :: ys) = case x `f` y of
True => x :: merge f xs (y :: ys)
False => y :: merge f (x :: xs) ys
и мы хотели бы доказать что-то умное в этом отношении, например, что объединение двух непустых списков дает непустой список:
mergePreservesNonEmpty : (f : Order a) ->
(xs : List a) -> (ys : List a) ->
{auto xsok : NonEmpty xs} -> {auto ysok : NonEmpty ys} ->
NonEmpty (merge f xs ys)
mergePreservesNonEmpty f (x :: xs) (y :: ys) = ?wut
Проверка типа отверстия wut
дает нам
wut : NonEmpty (case f x y of True => x :: merge f xs (y :: ys) False => y :: merge f (x :: xs) ys)
Пока имеет смысл! Итак, давайте продолжим и разделим регистр, как предлагает этот тип:
mergePreservesNonEmpty f (x :: xs) (y :: ys) = case x `f` y of
True => ?wut_1
False => ?wut_2
Кажется разумным надеяться, что типы wut_1
и wut_2
будут соответствовать соответствующим ветвям выражения case merge
(так что wut_1
будет чем-то вроде NonEmpty (x :: merge f xs (y :: ys))
, что может быть мгновенно удовлетворено), но наши надежды не оправдались: типы такие же, как и для оригинальный wut
.
Действительно, кажется, единственный способ - использовать предложение with
:
mergePreservesNonEmpty f (x :: xs) (y :: ys) with (x `f` y)
mergePreservesNonEmpty f (x :: xs) (y :: ys) | True = ?wut_1
mergePreservesNonEmpty f (x :: xs) (y :: ys) | False = ?wut_2
В этом случае типы будут такими, как ожидалось, но это приводит к повторению аргументов функции для каждой ветви with
(и все становится хуже, когда with
становится вложенным), плюс with
, похоже, не очень хорошо работает с неявными аргументами (но это, вероятно, того стоит вопрос сам по себе).
Итак, почему здесь не помогает case
, есть ли какие-либо причины, помимо чисто практических, которые не соответствуют его поведению с поведением with
, и есть ли другие способы написать это доказательство?
Материал слева от |
необходим только в том случае, если новая информация каким-то образом распространяется обратно к аргументам.
mergePreservesNonEmpty : (f : Order a) ->
(xs : List a) -> (ys : List a) ->
{auto xsok : NonEmpty xs} -> {auto ysok : NonEmpty ys} ->
NonEmpty (merge f xs ys)
mergePreservesNonEmpty f (x :: xs) (y :: ys) with (x `f` y)
| True = IsNonEmpty
| False = IsNonEmpty
-- for contrast
sym' : (() -> x = y) -> y = x
sym' {x} {y} prf with (prf ())
-- matching against Refl needs x and y to be the same
-- now we need to write out the full form
sym' {x} {y=x} prf | Refl = Refl
Что касается Зачем, то это так, я действительно считаю, что это просто реализация, но кто-то, кто знает лучше, может оспорить это.
Здесь подразумевается обратное распространение уточнение типов аргументов из-за типа шаблона в ветви with
.
@Cactus Думаю, я хорошо понимаю, что такое «уточнение», если это дает более точную информацию о конструкторе данного типа. Но что уточняется в типах в случае, подобном сопоставлению по f x y
, как в моем исходном вопросе? Опять же, не означает ли это, что каждый оператор if
/ case
каким-то образом уточняет типы?
@ 0xd34df00d Ничего. В этом суть этого ответа. Некоторые совпадения что-то раскрывают о структуре аргументов, и тогда вы должны использовать полную форму. У этого нет, поэтому аббревиатура действительна. Я не знаю точно, что @Cactus пытается сказать о типах аргументов, но важными вещами являются структура (какому выражению соответствует?) Аргументов и тип шаблона. В sym'
шаблон Refl
не будет хорошо типизирован, если x
и y
не идентичны, поэтому мы дорабатываем их, чтобы сделать его хорошо типизированным. Матч на Bool
всегда хорошо напечатан и ничего нам не говорит.
@HTNW ах, я должен научиться читать, как-то полностью пропустил отсутствие всего слева от |
в первый раз. Теперь я полностью понимаю, что вы имели в виду, спасибо!
Есть проблема с доказательством вещей с case
: https://github.com/idris-lang/Idris-dev/issues/4001
Из-за этого в Идрис-би нам в конечном итоге пришлось удалить все case
в таких функциях и определить отдельные помощники верхнего уровня, которые соответствуют условию case, например, как здесь.
Вам даже не нужно косвенное обращение к foo1
, я получаю то же самое, просто разделяя регистр на f
. На самом деле, это очень близко к моему следующему вопросу, так что я думаю, что я тоже задам его.
Я нахожу этот критерий распространения информации / воздействия на аргументы немного расплывчатым (например, каждый
case
влияет на то, что мы знаем об аргументах, так что именно это составляет?). Но, возможно, это что-то вроде интуиции, которую можно улучшить по мере написания большего количества кода Идриса.