Given the following type-level addition function on Peano numbers
sealed trait Nat
class O extends Nat
class S[N <: Nat] extends Nat
type plus[a <: Nat, b <: Nat] = a match
case O => b
case S[n] => S[n plus b]
say we want to prove theorem like
for all natural numbers n, n + 0 = n
which perhaps can be specified like so
type plus_n_0 = [n <: Nat] =>> (n plus O) =:= n
then when it comes to providing evidence for theorem we can easily ask Scala compiler for evidence in particular cases
summon[plus_n_O[S[S[O]]]] // ok, 2 + 0 = 2
but how can we ask Scala if it can generate evidence for all instantiations of [n <: Nat]
, thus providing proof of plus_n_0
?
Here is one possible approach, which is an attempt at a literal interpretation of this paragraph:
from the HoTT book (section 5.1).
Here is the plan of what was implemented in the code below:
Formulate what it means to have a proof for a statement that "Some property
P
holds for all natural numbers". Below, we will usewhere the signature of the
apply
-method essentially says "for alln <: N
, we can generate evidence ofP[n]
".Note that the method is declared to be
inline
. This is one possible way to ensure that the proof of∀n.P(n)
is constructive and executable at runtime (However, see edit history for alternative proposals with manually generated witness terms).Postulate some sort of induction principle for natural numbers. Below, we will use the following formulation:
I believe that it should be possible to
derive
such induction principles using some metaprogramming facilities.Write proofs for the base case and the induction case of the induction principle.
???
Profit
The code then looks like this:
It compiles, runs, and prints:
meaning that we have successfully invoked the recursively defined method on the executable evidence-term of type
two plus O =:= two
.Some further comments
trivialLemma
was necessary so thatsummon
s inside of othergiven
s don't accidentally generate recursive loops, which is a bit annoying.liftCo
-method forS[_ <: U]
was needed, because=:=.liftCo
does not allow type constructors with upper-bounded type parameters.compiletime.erasedValue
+inline match
is awesome! It automatically generates some sort of runtime-gizmos that allow us to do pattern matching on an "erased" type. Before I found this out, I was attempting to construct appropriate witness terms manually, but this does not seem necessary at all, it's provided for free (see edit history for the approach with manually constructed witness terms).