I'm not especially fond of So
, или, действительно, во избежание использования исключаемых терминов, работающих в программах. Это более удовлетворительно, чтобы сплести ваши ожидания в ткань самих данных. Я собираюсь записать тип для «натуральных чисел, меньших n
».
data Fin : Nat -> Set where
FZ : Fin (S n)
FS : Fin n -> Fin (S n)
Fin
представляет собой число типа типа данных - сравните форму FS (FS FZ)
с тем, что из натурального числа S (S Z)
- но с некоторой дополнительной структурой типа уровня. Почему это называется Fin
? Есть точно n
уникальных членов типа Fin n
; Fin
является, таким образом, семейством конечных множеств.
Я имею в виду: Fin
действительно является своего рода номером.
natToFin : (n : Nat) -> Fin (S n)
natToFin Z = FZ
natToFin (S k) = FS (natToFin k)
finToNat : Fin n -> Nat
finToNat FZ = Z
finToNat (FS i) = S (finToNat i)
Вот точка: значение Fin n
должно быть меньше, чем его n
.
two : Fin 3
two = FS (FS FZ)
two' : Fin 4
two' = FS (FS FZ)
badTwo : Fin 2
badTwo = FS (FS FZ) -- Type mismatch between Fin (S n) (Type of FZ) and Fin 0 (Expected type)
Пока мы находимся в нем, нет никаких чисел, меньших нуля. То есть Fin Z
, набор с мощностью 0, является пустым множеством.
Uninhabited (Fin Z) where
uninhabited FZ impossible
uninhabited (FS _) impossible
Если у вас есть число, которое меньше, чем n
, то это, конечно, меньше, чем S n
.Таким образом, у нас есть способ разрыхления верхней границы на Fin
:
weaken : Fin n -> Fin (S n)
weaken FZ = FZ
weaken (FS x) = FS (weaken x)
Мы также можем пойти другим путем, с помощью проверки типов автоматически найти сжатые возможно ограничение на данном Fin
.
strengthen : (i : Fin n) -> Fin (S (finToNat i))
strengthen FZ = FZ
strengthen (FS x) = FS (strengthen x)
можно смело определить вычитание Fin
чисел от Nat
чисел, которые больше. Мы также можем выразить тот факт, что результат не будет больше, чем вход.
(-) : (n : Nat) -> Fin (S n) -> Fin (S n)
n - FZ = natToFin n
(S n) - (FS m) = weaken (n - m)
То, что все работает, но есть одна проблема: weaken
работы по восстановлению своего аргумента в O (N) времени, и мы применяем его при каждом рекурсивном вызове -
, уступая O (п^2) алгоритм вычитания. Как неловко! weaken
действительно существует, чтобы помочь проверять тип, но он оказывает радикальное влияние на асимптотическую сложность кода. Можем ли мы уйти, не ослабив результат рекурсивного вызова?
Ну, нам нужно было позвонить weaken
, потому что каждый раз, когда мы сталкиваемся с S
, разница между результатом и границей возрастает. Вместо того, чтобы энергично вытягивать значение до правильного типа, мы можем закрыть зазор, осторожно потянув его вниз, чтобы встретить его.
(-) : (n : Nat) -> (i : Fin (S n)) -> Fin (S (n `sub` finToNat i))
n - FZ = natToFin n
(S n) - (FS i) = n - i
Этот тип вдохновлен нашего успеха в затягивании Fin
«S связанные с strengthen
. Оценка по результату -
точно такая же, как и должно быть.
sub
, который я использовал в типе, является вычитанием натуральных чисел. Тот факт, что он обрезается нулем, не беспокоит нас, потому что тип -
гарантирует, что он никогда не произойдет. (Эта функция может быть найдена в Prelude
под названием minus
.)
sub : Nat -> Nat -> Nat
sub n Z = n
sub Z m = Z
sub (S n) (S m) = sub n m
Урок, который следует извлечь здесь это. Сначала построение некоторых свойств корректности в наших данных вызвало асимптотическое замедление. Мы могли отказаться от обещаний о возврате и вернуться к нетипизированной версии, но фактически giving the type checker more information позволил нам добраться туда, куда мы направлялись, не жертвуя.
Я не слишком знаком с Идрисом, но это, безусловно, возможно. Я быстро взглянул на [stdlib] (https://github.com/idris-lang/Idris-dev/tree/master/libs/prelude/Prelude) - первым будет что-то вроде 'f: (nm: Nat) -> {isLT: Prelude.Nat.LT nm} -> X' и второй 'g: (x: String) (xs: List String) -> {in: Prelude.List.elem x xs = True} -> Y'. (где '=' является пропозициональным равенством, не уверен, что Идрис использует для этого?) Возможно, существуют и другие, более качественные кодировки, но они работают только с stdlib. – user2407038
@ user2407038, [it's] (http://docs.idris-lang.org/en/latest/tutorial/miscellany.html) '{auto isLT: Prelude.Nat.LT nm}' и '{auto in: Prelude .List.elem x xs = True} '. – user3237465
Это замечательно @ user2407038 Я замечаю, что когда я делаю '' 'f 50 100''', все в порядке. Но когда я делаю '' 'f 500 1000''', он заявляет, что не может найти решение. Я предполагаю, что это потому, что это грубо заставляет что-то? Есть ли способ получить тот же результат, который я хочу, чтобы работать для больших чисел? –