I would like to prove the following:
π-product-0 : β{l : π β} β list-any (_=β_ 0) l β‘ tt β π-product l β‘ 0
π-product-0 = {!!}
'list-any' is defined as:
list-any : β{β}{A : Set β}(pred : A β πΉ)(l : π A) β πΉ
list-any pred [] = ff
list-any pred (x :: xs) = pred x || list-any pred xs
And _=β_ is defined as:
_=β_ : β β β β πΉ
0 =β 0 = tt
suc x =β suc y = x =β y
_ =β _ = ff
I'm trying to understand this part: list-any (_=β_ 0) l β‘ tt
Is (_=β_ 0) β‘ (pred : A β πΉ) and l β‘ (l : π A)?
If so, I would like help understanding the predicate. What does (=β 0) mean? I'm assuming =β is applied like:
Foreach element in l return (element =β 0).
Is this correct? I tried to prove the theorem by using a list l1:
π-product-0 : β{l : π β} β list-any (_=β_ 0) l β‘ tt β π-product l β‘ 0
π-product-0 l1 = {!!}
but got the following error:
I'm not sure if there should be a case for the constructor refl,
because I get stuck when trying to solve the following unification
problems (inferred index β expected index):
list-any (_=β_ 0) l β tt
when checking that the expression ? has type π-product .l β‘ 0
I appreciate any help given!
Edit (response 1):
I split the the case on the hole and got:
π-product-0 : β{l : π β} β list-any (_=β_ 0) l β‘ tt β π-product l β‘ 0
π-product-0 x = {!!}
Is this the case that I want? It filled in x when I split.
It can also see that:
Goal: π-product .l β‘ 0
Where:
x : list-any (_=β_ 0) .l β‘ tt
.l : π β
What is the program wanting me to solve at this point? How can I show that
π-product-0 x is logically equivalent to π-product .l β‘ 0?
I'm trying to understand this part: list-any (=β 0) l β‘ tt
Is (=β 0) β‘ (pred : A β πΉ) and l β‘ (l : π A)?
You're correct in that _=β_ 0 is substituted for pred. _=β_ 0 means the result of applying the function _=β_ to 0. Since _=β_ is a binary function, applying it to just one argument yields a function β -> πΉ, which is what list-any expects.
As to the other question, you're trying to pattern match there on l, but it's implicit: in your example l1 actually denotes the list-any (_=β_ 0) l β‘ tt argument, because that's the first explicit argument. You have to introduce the implicit argument using brackets:
π-product-0 : β{l : π β} β list-any (_=β_ 0) l β‘ tt β π-product l β‘ 0
π-product-0 {l1} = {!!}
Edit:
I assume you're in Emacs agda-mode. When you look at the the context, the implicit arguments are prefixed with a dot, like .l : π β. If you're on a new-ish Agda, if you want to split on .l, write { .l } in the hole, then hit C-c-c. Another solution is to introduce the argument in the function definition using brackets, like how I wrote above, then write { l1 } in the hole, then hit C-c-c.
Alternatively, you can make the list argument explicit and save yourself the brackets:
π-product-0 : (l : π β) β list-any (_=β_ 0) l β‘ tt β π-product l β‘ 0
π-product-0 l = ?
Just remember that in a function definition, arguments without brackets are the explicit arguments, and you can insert implicit arguments before or between them corresponding to the order they appear in the type. You can also refer to implicit arguments by their name in the type. For example:
foo : Nat -> {l : list Nat} -> Nat -> Nat
foo n m = ? -- now "n" refers to the first Nat and "m" refers to the last argument
foo : Nat -> {l : list Nat} -> Nat -> Nat
foo n {l} m = ? -- now "l" refers to the list.
foo : Nat -> {l : list Nat} -> Nat -> Nat
foo n {l = list} m = ? -- refer to "l" by the name "list" here.
Related
I'm trying to show that the sum of two odd numbers is even.
What is wrong with the last line?
data odd : β β Set
data even : β β Set
data even where
ezero :
-------
even zero
esuc : β {n : β}
β odd n
------
β even (suc n)
data odd where
osuc : β { n : β }
β even n
------
β odd (suc n)
e+eβ‘e : β {m n : β}
β even m
β even n
----
β even (m + n)
o+eβ‘o : β {m n : β}
β odd m
β even n
------
β odd (m + n)
e+eβ‘e ezero en = en
e+eβ‘e (esuc om) en = esuc (o+eβ‘o om en)
o+eβ‘o (osuc em) en = osuc (e+eβ‘e em en)
o+oβ‘e : β {m n : β}
β odd m
β odd n
------
β even (m + n)
o+oβ‘e (osuc em) on = esuc (o+eβ‘o on em)
I'm getting this error:
β ξ° - 660 Experiment.agda ξ° Agda ξ° ξ² β ξ² unix | 50: 0 ξ² Bottom
/Users/max/dev/plfa.github.io/src/plfa/Experiment.agda:52,28-39
n != nβ of type β
when checking that the inferred type of an application
odd (n + _n_31)
matches the expected type
odd (nβ + n)
But the types seem fine to me. For example, if I replace the right side with ? and check the goals, Agda shows:
Goal: even (suc (n + nβ))
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
on : odd nβ
em : even n
nβ : β (not in scope)
n : β (not in scope
So I'm passing evidence on that n is odd and em that m is even. And passing these to o+eβ‘e, which expects arguments of exactly those types. So where did I go wrong?
And in general, how can I read Agda's error messages? Are the subscripts after variable names meaningful?
It's telling you that em is not equal to on: you want a proof of odd (m + n), but you get odd (n + m) - Agda can't see addition is commutative. You should swap the arguments.
o+oβ‘e on (osuc em) = esuc (o+eβ‘o on em)
This produces a different error. That error tells you that Agda is unable to work out that suc (m + n) is equal to m + suc n, which means you need to introduce a lemma that establishes the equality. Then recall transport (a function that transports a value of a dependent type B x along equality x β‘ y to a value of a different dependent type B y), and that will give you a way to obtain a value of the needed type from the value that esuc (o+eβ‘o on em) constructs.
Working solution with zero imports:
data _==_ {A : Set} (x : A) : A -> Set where
refl : x == x
-- congruence
cong : forall {A B : Set} {x y : A} -> (f : A -> B) -> (x == y) -> (f x) == (f y)
cong f refl = refl -- note these refls are of different types: of x == y on the left, and of (f x) == (f y) on the right
-- transport: given two values are "equal", transport one dependent value along the equality path into a different dependent value
transport : forall {A : Set} {B : A -> Set} {x y : A} -> x == y -> B x -> B y
transport refl bx = bx -- proof relies on the circumstance that the only way to construct x == y is refl, so (B x) is (B y)
-- then induction at the heart of Agda can work out that this must be valid for any x == y
-- commutativity of _==_
comm : forall {A : Set} {x y : A} -> x == y -> y == x
comm refl = refl
data Nat : Set where
zero : Nat
suc : Nat -> Nat
_+_ : β (m n : Nat) -> Nat
zero + n = n
(suc m) + n = suc (m + n)
-- Proving the necessary commutativity of suc.
-- Agda can see things like "(suc m) + n == suc (m + n)" by definition
-- but other equalities need proving, and then you can transport
-- the values from one type to another
n+1β‘1+n : forall (m n : Nat) -> (m + (suc n)) == (suc (m + n))
n+1β‘1+n zero n = refl
n+1β‘1+n (suc m) n = cong suc (n+1β‘1+n m n)
data odd : Nat β Set
data even : Nat β Set
data even where
ezero :
-------
even zero
esuc : β {n : Nat}
β odd n
------
β even (suc n)
data odd where
osuc : β { n : Nat }
β even n
------
β odd (suc n)
e+eβ‘e : β {m n : Nat}
β even m
β even n
----
β even (m + n)
o+eβ‘o : β {m n : Nat}
β odd m
β even n
------
β odd (m + n)
e+eβ‘e ezero en = en
e+eβ‘e (esuc om) en = esuc (o+eβ‘o om en)
o+eβ‘o (osuc em) en = osuc (e+eβ‘e em en)
-- Prove commutativity of even based on a known proof for commutativity of suc.
e-comm : forall {m n : Nat} -> even (suc (m + n)) -> even (m + (suc n))
e-comm {m} {n} esmn = transport {B = even} (comm (n+1β‘1+n m n)) esmn -- transport needs hinting what B is
-- otherwise Agda cannot infer what B is based on the definition as found in this snippet
-- the error may seem a bit obscure, but you can see it is wrangling with
-- the dependent type of B:
-- Failed to solve the following constraints:
-- _74 := Ξ» {m} {n} esmn β transport (comm (n+1β‘1+n m n)) (_72 esmn)
-- [blocked on problem 166]
-- [165] (even (suc (m + n))) =< (_B_73 (suc (m + n))) : Set
-- [166] _B_73 (m + suc n) =< even (m + suc n) : Set
-- _71 := (Ξ» {m} {n} esmn β esmn) [blocked on problem 165]
--
-- See, it is stuck trying to work out a type _B_73 such that even
-- would be a subtype of it, and a different even would be a supertype of it.
o+oβ‘e : β {m n : Nat}
β odd m
β odd n
------
β even (m + n)
o+oβ‘e {m} om (osuc en) = e-comm {m} (esuc (o+eβ‘o om en)) -- Agda had a problem working out m, so extracting it from implicits
-- Failed to solve the following constraints:
-- _81 := Ξ» {.n} {.m} om en β e-comm (_80 om en)
-- [blocked on problem 188]
-- [188, 189] _m_74 om en + suc (_n_75 om en) = .m + suc .n : Nat
-- _79 := Ξ» {.n} {.m} om en β esuc (o+eβ‘o om en)
-- [blocked on problem 185]
-- [185, 186, 187] .m + .n = _m_74 om en + _n_75 om en : Nat
--
-- See, if e-comm is not given {m} and {n}, then it is stuck working out
-- _m_74
transport joining dependent types is one of the key concepts. For example, congruence and commutativity of _==_ can be reduced to transport:
-- congruence
cong : forall {A B : Set} {x y : A} -> (f : A -> B) -> (x == y) -> (f x) == (f y)
cong {x = x} f xy = transport {B = (\y -> (f x) == (f y))} -- just making explicit that B is a type (f x) == (f _)
xy refl -- this refl is of type (f x) == (f x), which gets transported along x == y to (f x) == (f y)
-- commutativity of _==_
comm : forall {A : Set} {x y : A} -> x == y -> y == x
comm {x = x} xy = transport {B = (_== x)} xy refl -- this refl is of type x == x, which gets transported along x == y to y == x
I am a noob in agda and reading http://www.cse.chalmers.se/~ulfn/papers/afp08/tutorial.pdf. My shallow knowledge somehow finds dot pattern not quite necessary. For example,
data Image_β_ {A B : Set}(f : A β B) : B β Set where
im : (x : A) β Image f β f x
inv : {A B : Set}(f : A β B)(y : B) β Image f β y β A
inv f .(f x) (im x) = x
I find inv can well be defined as
inv : {A B : Set}(f : A β B)(y : B) β Image f β y β A
inv _ _ (im x) = x
because from the types, we've already known y is an image of f for some x, so it cannot possibly go wrong.
Another example is
data _==_ {A : Set}(x : A) : A β Set where
refl : x == x
data _β _ : β β β β Set where
zβ s : {n : β} β zero β suc n
sβ z : {n : β} β suc n β zero
sβ s : {n m : β} β n β m β suc n β suc m
data Equal? (n m : β) : Set where
eq : n == m β Equal? n m
neq : n β m β Equal? n m
equal? : (n m : β) β Equal? n m
equal? zero zero = eq refl
equal? zero (suc _) = neq zβ s
equal? (suc _) zero = neq sβ z
equal? (suc n') (suc m') with equal? n' m'
... | eq refl = eq refl
... | neq n'β m' = neq (sβ s n'β m')
consider equal? function, the second last line is written in the paper as (suc n') (suc .n') | eq refl = eq refl. Again, eq refl in with construct has provided a proof, for these two values being the same, so why do I bother writing them out using dot pattern?
I am more familiar with coq, and I am not aware of similar thing in coq. Am I missing something here?
In Coq you write the pattern-matches explicitly whereas Agda's equation-based approaches forces the typechecker to reconstruct a case-tree which ought to correspond to what you wrote.
Dotted-patterns help the typechecker see that a given pattern was not the product of a match but rather forced by a match on one of the other arguments (e.g.: a match on a Vec Bool n will force the value of n, or a match on an equality proof will, as you've observed, force some variables to be the same).
They're not always necessary and, in fact, some have been slowly made optional as you can see in the CHANGELOG for version 2.5.3:
Dot patterns.
The dot in front of an inaccessible pattern can now be skipped if the pattern consists entirely of constructors or literals. For example:
The code is:
filter-pos : {A : Set} β π A β (β β πΉ) β π A
filter-pos = {!!}
filter-pos-test : filter-pos ('a' :: 'b' :: 'c' :: 'd' :: 'e' :: 'f' :: []) is-even β‘ 'a' :: 'c' :: 'e' :: []
filter-pos-test = refl
My thought is to use nth to output nth index, use map function to make them into a list, and if it's a even number n, it will return. However, that is not working correctly.
Can someone let me know how I should go about solving this? I think it will be helpful to write a help function to solve the problem.
I'll be using the standard library.
One dumb solution is to zip a list with indices of elements, use the usual filter and then remove the indices. E.g.
'a' :: 'b' :: 'c' :: 'd' :: 'e' :: 'f' :: []
becomes
('a', 0) :: ('b', 1) :: ('c', 2) :: ('d', 3) :: ('e', 4) :: ('f', 5) :: []
filter (is-even β snd) returns
('a', 0) :: ('c', 2) :: ('e', 4) :: []
and map fst results in
'a' :: 'c' :: 'e' :: []
A more natural solution is to traverse a list and increment a counter on each recursive call:
filter-pos : {A : Set} β List A β (β β Bool) β List A
filter-pos {A} xs p = go 0 xs where
go : β -> List A -> List A
go i [] = []
go i (x β· xs) = if p i then x β· r else r where
r = go (suc i) xs
Here i is the index of an element. However now whenever you need to prove something about filter-pos, you'll need to prove a lemma about go first, because it's go does the actual job, while filter-pos is just a wrapper around it. An idiomatic solution looks like this:
filter-pos : {A : Set} β List A β (β β Bool) β List A
filter-pos [] p = []
filter-pos (x β· xs) p = if p 0 then x β· r else r where
r = filter-pos xs (p β suc)
Here instead of incrementing a counter we adjust a predicate and compose it with suc. So on a first element we check whether p 0 is true, on a second element we check whether (p β suc) 0 (which immediately reduces to p 1) is true, on a third element we check whether (p β suc β suc) 0 (which immediately reduces to p 2) is true and so on. I.e. this is the same solution as with a counter, but uses only one function.
The last version also can be tuned to work with Fin instead of β
filter-pos-fin : {A : Set} β (xs : List A) β (Fin (length xs) β Bool) β List A
filter-pos-fin [] p = []
filter-pos-fin (x β· xs) p = if p zero then x β· r else r where
r = filter-pos-fin xs (p β suc)
In System F, the kind of a polymorphic type is * (as that's the only kind in System F anyway...), so e.g. for the following closed type:
[] β’ (forall Ξ± : *. Ξ± β Ξ±) : *
I would like to represent System F in Agda, and because everything is in *, I thought I'd interpret types (like the above) as Agda Sets; so something like
evalTy : RepresentationOfAWellKindedClosedType β Set
However, Agda doesn't have polymorphic types, so the above type, in Agda, would need to be a (large!) Ξ type:
idType = (Ξ± : Set) β Ξ± β Ξ±
which means it is not in Setβ:
idType : Set
idType = (Ξ± : Set) β Ξ± β Ξ±
poly-level.agda:4,12-29
Setβ != Set
when checking that the expression (Ξ± : Set) β Ξ± β Ξ± has type Set
Is there a way out of this, or is System F not embeddable in this sense into Agda?
Instead of
evalTy : Type β Set
you can write
evalTy : (Ο : Type) -> Set (levelOf Ο)
(AndrΓ‘s KovΓ‘cs, care to add an answer with references to your embedding of predicative System F?)
This is enough for embedding, but I've seen a lot of SetΟ errors and they have traumatised me, so now I'm trying to avoid dependent universes as much as possible.
You can embed polymorphic types into Setβ and monomorphic types into Set, so you can embed any type into Setβ using the ugly lifting mechanism. I tried this several times and it always was awful.
The thing I would try is to define evalTy as
evalTy : Type -> Set β Setβ
and then eliminate it at the type level like this:
data [_,_]α΅ {Ξ± Ξ² Ξ³ Ξ΄} {A : Set Ξ±} {B : Set Ξ²} (C : A -> Set Ξ³) (D : B -> Set Ξ΄)
: A β B -> Set (Ξ³ β Ξ΄) where
injΒΉ : β {x} -> C x -> [ C , D ]α΅ (injβ x)
injΒ² : β {y} -> D y -> [ C , D ]α΅ (injβ y)
You can run this elimination:
Runα΄Έ : β {Ξ± Ξ² Ξ³ Ξ΄} {A : Set Ξ±} {B : Set Ξ²} {C : A -> Set Ξ³} {D : B -> Set Ξ΄} {s}
-> [ C , D ]α΅ s -> Level
Runα΄Έ {Ξ³ = Ξ³} (injΒΉ _) = Ξ³
Runα΄Έ {Ξ΄ = Ξ΄} (injΒ² _) = Ξ΄
Runα΅ : β {Ξ± Ξ² Ξ³ Ξ΄} {A : Set Ξ±} {B : Set Ξ²} {C : A -> Set Ξ³} {D : B -> Set Ξ΄} {s}
-> (sα΅ : [ C , D ]α΅ s) -> Set (Runα΄Έ sα΅)
Runα΅ {C = C} (injΒΉ {x} _) = C x
Runα΅ {D = D} (injΒ² {y} _) = D y
runα΅ : β {Ξ± Ξ² Ξ³ Ξ΄} {A : Set Ξ±} {B : Set Ξ²} {C : A -> Set Ξ³} {D : B -> Set Ξ΄} {s}
-> (sα΅ : [ C , D ]α΅ s) -> Runα΅ sα΅
runα΅ (injΒΉ z) = z
runα΅ (injΒ² w) = w
Thus you introduce a universe dependency only at the end, when you actually need to compute something.
E.g.
SomeSet : β -> Set β Setβ
SomeSet 0 = injβ β
SomeSet n = injβ Set
ofSomeSet : β n -> [ (Ξ» A -> A Γ A) , id ]α΅ (SomeSet n)
ofSomeSet zero = injΒΉ (0 , 5)
ofSomeSet (suc n) = injΒ² β
-- 0 , 5
testβ : β Γ β
testβ = runα΅ (ofSomeSet 0)
-- β
testβ : Set
testβ = runα΅ (ofSomeSet 1)
ofSomeSet is a dependent function, but not a "universally dependent", you can write e.g. f = ofSomeSet β suc and it's a perfectly typeable expression. This doesn't work with universes dependencies:
fun : β Ξ± -> Set (Level.suc Ξ±)
fun Ξ± = Set Ξ±
oops = fun β Level.suc
-- ((Ξ± : Level) β Set (Level.suc Ξ±)) !=< ((y : _B_160 .x) β _C_161 y)
-- because this would result in an invalid use of SetΟ
You can also enhance [_,_]α΅ to make it mappable like I did here, but this all is probably overkill and you should just use
evalTy : (Ο : Type) -> Set (levelOf Ο)
Note that I'm talking only about the predicative fragment of System F. Full System F is not embeddable as Dominique Devriese explains in his comments to the question.
However I feel like we can embed more than the predicative fragment, if we first normalize a System F term. E.g. id [β Ξ± : *. Ξ± β Ξ±] id is not directly embeddable, but after normalization it becomes just id, which is embeddable.
However it should be possible to embed id [β Ξ± : *. Ξ± β Ξ±] id even without normalization by transforming it into Ξ Ξ±. id [Ξ± β Ξ±] (id [Ξ±]), which is what Agda does with implicit arguments (right?). So it's not clear to me what exactly we can't embed.
I am learning how "typeclasses" are implemented in Agda. As an example, I am trying to implement Roman numerals whose composition with # would type-check.
I am not clear why Agda complains there is no instance for Join (Roman _ _) (Roman _ _) _ - clearly, it couldn't work out what natural numbers to substitute there.
Is there a nicer way to introduce Roman numbers that don't have "constructor" form? I have a constructor "madeup", which probably would need to be private, to be sure I have only "trusted" ways to construct other Roman numbers through Join.
module Romans where
data β : Set where
zero : β
succ : β β β
infixr 4 _+_ _*_ _#_
_+_ : β β β β β
zero + x = x
succ y + x = succ (y + x)
_*_ : β β β β β
zero * x = zero
succ y * x = x + (y * x)
one = succ zero
data Roman : β β β β Set where
i : Roman one one
{- v : Roman one five
x : Roman ten one
... -}
madeup : β {a b} (x : Roman a b) β (c : β) β Roman a c
record Join (A B C : Set) : Set where
field jo : A β B β C
two : β {a} β Join (Roman a one) (Roman a one) (Roman a (one + one))
two = record { jo = Ξ» l r β madeup l (one + one) }
_#_ : β {a b c d C} β {{j : Join (Roman a b) (Roman c d) C}} β Roman a b β Roman c d β C
(_#_) {{j}} = Join.jo j
-- roman = (_#_) {{two}} i i -- works
roman : Roman one (one + one)
roman = {! i # i!} -- doesn't work
Clearly, if I specify the implicit explicitly, it works - so I am confident it is not the type of the function that is wrong.
Your example works fine in development version of Agda. If you are using a version older than 2.3.2, this passage from release notes could clarify why it doesn't compile for you:
* Instance arguments resolution will now consider candidates which
still expect hidden arguments. For example:
record Eq (A : Set) : Set where
field eq : A β A β Bool
open Eq {{...}}
eqFin : {n : β} β Eq (Fin n)
eqFin = record { eq = primEqFin }
testFin : Bool
testFin = eq fin1 fin2
The type-checker will now resolve the instance argument of the eq
function to eqFin {_}. This is only done for hidden arguments, not
instance arguments, so that the instance search stays non-recursive.
(source)
That is, before 2.3.2, the instance search would completly ignore your two instance because it has a hidden argument.
While instance arguments behave a bit like type classes, note that they will only commit to an instance if there's only one type correct version in scope and they will not perform a recursive search:
Instance argument resolution is not recursive. As an example,
consider the following "parametrised instance":
eq-List : {A : Set} β Eq A β Eq (List A)
eq-List {A} eq = record { equal = eq-List-A }
where
eq-List-A : List A β List A β Bool
eq-List-A [] [] = true
eq-List-A (a β· as) (b β· bs) = equal a b β§ eq-List-A as bs
eq-List-A _ _ = false
Assume that the only Eq instances in scope are eq-List and eq-β.
Then the following code does not type-check:
test = equal (1 β· 2 β· []) (3 β· 4 β· [])
However, we can make the code work by constructing a suitable
instance manually:
testβ² = equal (1 β· 2 β· []) (3 β· 4 β· [])
where eq-List-β = eq-List eq-β
By restricting the "instance search" to be non-recursive we avoid
introducing a new, compile-time-only evaluation model to Agda.
(source)
Now, as for the second part of the question: I'm not exactly sure what your final goal is, the structure of the code ultimately depends on what you want to do once you construct the number. That being said, I wrote down a small program that allows you to enter roman numerals without going through the explicit data type (forgive me if I didn't catch your intent clearly):
A roman numeral will be a function which takes a pair of natural numbers - the value of previous numeral and the running total. If it's smaller than previous numeral, we'll subtract its value from the running total, otherwise we add it up. We return the new running total and value of current numeral.
Of course, this is far from perfect, because there's nothing to prevent us from typing I I X and we end up evaluating this as 10. I leave this as an exercise for the interested reader. :)
Imports first (note that I'm using the standard library here, if you do not want to install it, you can just copy the definition from the online repo):
open import Data.Bool
open import Data.Nat
open import Data.Product
open import Relation.Binary
open import Relation.Nullary.Decidable
This is our numeral factory:
_<?_ : Decidable _<_
m <? n = suc m β€? n
makeNumeral : β β β Γ β β β Γ β
makeNumeral n (p , c) with β n <? p β
... | true = n , c βΈ n
... | false = n , c + n
And we can make a few numerals:
infix 500 I_ V_ X_
I_ = makeNumeral 1
V_ = makeNumeral 5
X_ = makeNumeral 10
Next, we have to apply this chain of functions to something and then extract the running total. This is not the greatest solution, but it looks nice in code:
β§ : β Γ β
β§ = 0 , 0
infix 400 β¦_
β¦_ : β Γ β β β
β¦ (_ , c) = c
And finally:
testβ : β
testβ = β¦ X I X β§
testβ : β
testβ = β¦ X I V β§
Evaluating testβ via C-c C-n gives us 19, testβ then 14.
Of course, you can move these invariants into the data type, add new invariants and so on.