I'm not especially fond of So
, o addirittura di avere termini di prova evitabili che si aggirano nei programmi. È più soddisfacente tessere le tue aspettative nel tessuto dei dati stessi. Scriverò un tipo per "numeri naturali più piccoli di n
".
data Fin : Nat -> Set where
FZ : Fin (S n)
FS : Fin n -> Fin (S n)
Fin
è un tipo di dati numerico come - confrontare la forma di FS (FS FZ)
con quella del numero naturale S (S Z)
- ma con ulteriori strutture tipo-livello. Perché si chiama Fin
? Esistono precisamente n
membri univoci del tipo Fin n
; Fin
è quindi la famiglia di set finiti.
Voglio dire: Fin
è davvero una sorta di numero.
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)
Ecco il punto: un valore Fin n
deve essere più piccolo rispetto al suo 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)
Mentre ci siamo, non ci sono numeri minori di zero. Vale a dire, Fin Z
, un set con cardinalità pari a 0, è un set vuoto.
Uninhabited (Fin Z) where
uninhabited FZ impossible
uninhabited (FS _) impossible
Se si dispone di un numero che è meno di n
, allora è certamente meno di S n
.Abbiamo così un modo per allentare il limite superiore su un Fin
:
weaken : Fin n -> Fin (S n)
weaken FZ = FZ
weaken (FS x) = FS (weaken x)
Possiamo anche andare nella direzione opposta, utilizzando il tipo di controllo per trovare automaticamente il più stretto possibile, legato su un dato Fin
.
strengthen : (i : Fin n) -> Fin (S (finToNat i))
strengthen FZ = FZ
strengthen (FS x) = FS (strengthen x)
si può definire tranquillamente la sottrazione di numeri da Fin
Nat
numeri che sono più grandi. Possiamo anche esprimere il fatto che il risultato non sarà più grande dell'input.
(-) : (n : Nat) -> Fin (S n) -> Fin (S n)
n - FZ = natToFin n
(S n) - (FS m) = weaken (n - m)
che tutte le opere, ma c'è un problema: weaken
opere ricostruendo il suo argomento a O (n), e stiamo applicarlo ad ogni chiamata ricorsiva di -
, ottenendo un O (n^2) algoritmo per la sottrazione. Che imbarazzo! weaken
è davvero lì solo per aiutare il controllo dei caratteri, ma ha un drastico effetto sulla complessità asintotica del tempo del codice. Possiamo andare via senza indebolire il risultato della chiamata ricorsiva?
Bene, abbiamo dovuto chiamare weaken
perché ogni volta che incontriamo un S
, la differenza tra il risultato e il limite aumenta. Invece di tirare con forza il valore fino al tipo corretto, possiamo chiudere lo spazio tirandolo delicatamente verso il basso per incontrarlo.
(-) : (n : Nat) -> (i : Fin (S n)) -> Fin (S (n `sub` finToNat i))
n - FZ = natToFin n
(S n) - (FS i) = n - i
Questo tipo si ispira il nostro successo nello stringere un Fin
's legate con strengthen
. Il limite sul risultato di -
è esattamente il limite necessario.
sub
, che ho utilizzato nel tipo, è la sottrazione di numeri naturali. Il fatto che tronca a zero non ci deve preoccupare, perché il tipo di -
assicura che non accadrà mai realmente. (Questa funzione può essere trovato nel Prelude
sotto il nome di minus
.)
sub : Nat -> Nat -> Nat
sub n Z = n
sub Z m = Z
sub (S n) (S m) = sub n m
La lezione da imparare qui è questo. In un primo momento, la costruzione di alcune proprietà di correttezza nei nostri dati ha causato un rallentamento asintotico. Avremmo potuto rinunciare a fare promesse sul valore di ritorno e tornare a una versione non tipizzata, ma in effetti giving the type checker more information ci ha permesso di arrivare dove stavamo andando senza fare sacrifici.
Non ho familiarità con Idris ma è certamente possibile. Ho dato una rapida occhiata a [stdlib] (https://github.com/idris-lang/Idris-dev/tree/master/libs/prelude/Prelude) - il primo sarebbe qualcosa come 'f: (nm: Nat) -> {isLT: Prelude.Nat.LT nm} -> X' e il secondo 'g: (x: String) (xs: List String) -> {in: Prelude.List.elem x xs = True} -> Y'. (dove '=' è l'uguaglianza proposizionale, non è sicuro che il simbolo che Idris usa per questo?) Ci sono forse altre codifiche migliori, ma queste funzionano solo con lo stdlib. – user2407038
@ user2407038, [è] (http://docs.idris-lang.org/en/latest/tutorial/miscellany.html) '{auto isLT: Prelude.Nat.LT nm}' e '{auto in: Prelude .List.elem x xs = True} '. – user3237465
Questo è ottimo @ user2407038 Noto che quando faccio '' 'f 50 100''' va tutto bene. Ma quando faccio '' 'f 500 1000''' afferma che non riesce a trovare una soluzione. Suppongo che sia perché è bruto forzare qualcosa? C'è un modo per avere lo stesso risultato che voglio che funzioni per numeri più grandi? –