Also have a look at
- latex/yoneda.pdf
or
- latex/yonedaWithCode.pdf
(latex/yonedaWithCode.pdf is not fully in sync with this document and src (yet))
All comments are welcome at luc.duponcheel [at] gmail.com
The Yoneda lemma is a fundamantal result of category theory.
The Yoneda lemma is simple.
It involves only three concepts
- category,
- functory,
- natural transformation,
and one category instance
- the category of sets and functions.
Moreover, all functors involved are funtors to the category of sets and functions.
The simplicity of the Yoneda lemma does not imply in any way that the Yoneda lemma is easy.
The Yoneda lemma is pointful: it involves elements of sets.
In fact, being pointful is the essence of the Yoneda lemma.
The Yoneda lemma is also simple.
Our Yoneda lemma involves four concepts
- functional category,
- endofunctor,
- endo natural transformation,
- triple,
and one triple instance
- the triple of global elements.
Functional categories model, perhaps not surprisingly, functional programming.
Their morphisms are also referred to as programs.
The class of objects of functional categories is the class of sets of the category of sets and functions. The functions of the category of sets and functions can, somehow, be used as programs of functional categories. Functional categories may have programs that are not functions. Most notably effectful programs, like stateful programs or programs that interact with the outside world.
The Yoneda lemma is pointfree.
The category of sets and functions does not play an explicit role any more.
In this document, the formulation and proof of both the pointful Yoneda lemma and our pointfree Yoneda lemma
use programmatic notation. More precisely we use Scala
code. Concepts are typically encoded as trait
's and
instances are typically encoded as given
's.
There is one important implication of using programmatic notation. The class of objects is encoded as the set
of Scala
types. Fortunately, it turns out that, both for the proof of the pointful Yoneda lemma and the proof
of our pointfree Yoneda lemma this is not an issue at all. The proofs can easily be translated proofs that use
mathematical notation.
Let's start with the concepts involved.
Later on laws that come with concepts will be defined.
package specification
trait Category[C[-_, +_]]:
def identity[Z]: C[Z, Z]
def composition[Z, Y, X]: (C[Y, X], C[Z, Y]) => C[Z, X]
extension [Z, Y, X](`y-->x`: C[Y, X]) def o(`z-->y`: C[Z, Y]): C[Z, X] = composition(`y-->x`, `z-->y`)
The Category
specification for a binary type constructor parameter C[-_, +_]
declares def
members,
identity
and composition
, and defines an extension
member, o
, that is a convenient an infix version of
composition
.
Category
specifies a (higher kinded) type class for C[-_, +_]
.
The -
and +
in C[-_, +_]
declare variance. Like functions of type Function[Z, Y]
, values of type
C[Z, Y]
are allowed to require less and provide more (cfr. Liskov substitution principle). Variance is not
used in either the pointful Yoneda lemma or our pointfree Yoneda lemma.
Category parameters are named C
, D
, E
, ... .
Maybe it would be more convenient to name them something like Cat_C
, Cat_D
, Cat_E
, ... .
package specification
trait Functor[C[-_, +_]: Category, D[-_, +_]: Category, F[+_]]:
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]]
type EndoFunctor[C[-_, +_], F[+_]] = Functor[C, C, F]
The Functor
specification for a unary type constructor parameter F[+_]
declares a def
member f
.
Functor
specifies a (higher kinded) type class for F[+_]
.
The +
in F[+_]
declares variance. Like lists of type List[Z]
, values of type F[Z]
are allowed to
provide more (cfr. Liskov substitution principle). Variance is not used in either the pointful Yoneda lemma or
our pointfree Yoneda lemma.
Note that f
is a function
type
EndoFunctor
is defined using Functor
using only one category.
Functor parameters are named F
, G
, H
, K
, ... .
Maybe it would be more convenient to name them something like Fun_F
, Fun_G
, Fun_H
, Fun_K
, ... .
package specification
trait NaturalTransformation[
C[-_, +_]: Category,
D[-_, +_]: Category,
F[+_]: [_[+_]] =>> Functor[C, D, F],
G[+_]: [_[+_]] =>> Functor[C, D, G]
]:
def τ[Z]: D[F[Z], G[Z]]
type EndoNaturalTransformation[C[-_, +_], F[+_], G[+_]] = NaturalTransformation[C, C, F, G]
The NaturalTransformation
specification declares a def
member τ
.
The trait
does not have a (higher kinded) type parameter like the C[-_, +_]
of Category
resp. the
F[+_]
of Functor
.
NaturalTransformation
specifies an object class for natural transformations.
Note that, in the sentence above, the word 'object' has to be understood in the context of object oriented programming instead of in the context of category theory
Note that τ
is a morphism.
The def
members of various natural transformations will be renamed as σ
, τ
, υ
, ν
, μ
... .
type
EndoNaturalTransformation
is defined using NaturalTransformation
using endofunctors.
Let
package notation
type I[+Z] = Z
in
package specification
import notation.{I}
trait PreTriple[C[-_, +_]: Category, PT[+_]: [_[+_]] =>> EndoFunctor[C, PT]]:
val nu: EndoNaturalTransformation[C, I, PT]
def pt[Z, Y]: C[Z, Y] => C[PT[Z], PT[Y]] = summon[EndoFunctor[C, PT]].f
def ν[Z]: C[I[Z], PT[Z]] = nu.τ
The PreTriple
specification for an endofunctor PT[+_]
declares a val
member nu
that is an endo natural
transformation from I[+_]
to PT[+_]
and defines pt
corresponding to function f
and ν
(Greek letter
nu (for neutral)) corresponding to morphism τ
of nu
.
Later on the identity endofunctor for the unary type constructor I[+_]
will be defined.
Let
package notation
type O = [G[+_], F[+_]] =>> [Z] =>> G[F[Z]]
in
package specification
import notation.{O}
trait Triple[C[-_, +_]: Category, T[+_]: [_[+_]] =>> EndoFunctor[C, T]] extends PreTriple[C, T]:
val mu: EndoNaturalTransformation[C, T O T, T]
val tef: EndoFunctor[C, T] = summon[EndoFunctor[C, T]]
def t[Z, Y]: C[Z, Y] => C[T[Z], T[Y]] = tef.f
def μ[Z]: C[(T O T)[Z], T[Z]] = mu.τ
The Triple
specification for an endofunctor T[+_]
declares a val
member mu
that is an endo natural
transformation from (T O T)[+_]
to T[+_]
and defines t
corresponding to function f
and μ
(Greek
letter 'mu' (for multiplication)) corresponding to the morphism τ
of mu
.
Note that also tef
has been defined in order to be able to access the endofunctor T[+_]
outside of
Triple
. There may be other ways to accomplish this.
Later on the composed functor (G O F)[+_]
for the binary unary type constructor O
will be defined.
We are done with our basic specifications.
Let's start with some basic implementations.
package implementation.specific
import specification.{Category}
given functionCategory: Category[Function] with
def identity[Z]: Function[Z, Z] = z => z
def composition[Z, Y, X]: (Function[Y, X], Function[Z, Y]) => Function[Z, X] =
(`y=>x`, `z=>y`) => z => `y=>x`(`z=>y`(z))
The specific functionCategory
implementation for the binary type constructor Function[-_, +_]
defines def
members, identity
and composition
in a, hopefully, not very surprising way.
package implementation.generic
import notation.{I}
import specification.{Category, EndoFunctor}
given identityEndoFunctor[C[-_, +_]: Category]: EndoFunctor[C, I] with
def f[Z, Y]: C[Z, Y] => C[I[Z], I[Y]] = `z-->y` => `z-->y`
The generic identityEndoFunctor
implementation for the unary type constructor I[+_]
defines def
member
f
in a, hopefully, not very surprising way.
package implementation.generic
import notation.{O}
import specification.{Category, Functor}
given composedFunctor[
C[-_, +_]: Category,
D[-_, +_]: Category,
E[-_, +_]: Category,
F[+_]: [_[+_]] =>> Functor[C, D, F],
G[+_]: [_[+_]] =>> Functor[D, E, G]
]: Functor[C, E, G O F] with
def f[Z, Y]: C[Z, Y] => E[(G O F)[Z], (G O F)[Y]] =
def f: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
def g: D[F[Z], F[Y]] => E[(G O F)[Z], (G O F)[Y]] = summon[Functor[D, E, G]].f
`z-->y` => g(f(`z-->y`))
The generic composedFunctor
implementation (G O F)[+_]
for unary type constructor G[+_]
and unary type
constructor F[+-]
defines def
member f
in a, hopefully, not very surprising way.
package implementation.generic
import notation.{I}
import specification.{Category, EndoNaturalTransformation}
def identityEndoNaturalTransformation[C[-_, +_]: Category]: EndoNaturalTransformation[C, I, I] =
new:
def τ[Z]: C[I[Z], I[Z]] =
summon[Category[C]].identity
The generic identityEndoNaturalTransformation
implementation defines def
member τ
in a, hopefully, not
very surprising way.
Note that identityEndoNaturalTransformation
is a def
.
package implementation.generic
import specification.{Category, EndoFunctor, EndoNaturalTransformation}
def composedNaturalTransformation[
C[-_, +_]: Category,
F[+_]: [_[+_]] =>> EndoFunctor[C, F],
G[+_]: [_[+_]] =>> EndoFunctor[C, G],
H[+_]: [_[+_]] =>> EndoFunctor[C, H],
S[_[-_, +_], _[-_, +_], _[+_], _[+_]]: [_[
_[-_, +_],
_[-_, +_],
_[+_],
_[+_]
]] =>> EndoNaturalTransformation[
C,
F,
G
],
T[_[-_, +_], _[-_, +_], _[+_], _[+_]]: [_[
_[-_, +_],
_[-_, +_],
_[+_],
_[+_]
]] =>> EndoNaturalTransformation[
C,
G,
H
]
]: EndoNaturalTransformation[C, F, H] =
def σ[Z]: C[F[Z], G[Z]] = summon[EndoNaturalTransformation[C, F, G]].τ
def τ[Z]: C[G[Z], H[Z]] = summon[EndoNaturalTransformation[C, G, H]].τ
def υ[Z]: C[F[Z], H[Z]] = τ o σ
new:
def τ[Z]: C[F[Z], H[Z]] = υ
The generic composedNaturalTransformation
implementation defines def
member τ
in a, hopefully, not
very surprising way.
Note that composedNaturalTransformation
is a def
.
package implementation.generic
import notation.{O}
import specification.{Category, Functor, NaturalTransformation}
def leftFunctorComposedNaturalTransformation[
C[-_, +_]: Category,
D[-_, +_]: Category,
E[-_, +_]: Category,
H[+_]: [_[+_]] =>> Functor[D, E, H],
F[+_]: [_[+_]] =>> Functor[C, D, F],
G[+_]: [_[+_]] =>> Functor[C, D, G]
](sigma: NaturalTransformation[C, D, F, G]): NaturalTransformation[C, E, H O F, H O G] =
given `hof`: Functor[C, E, H O F] = composedFunctor[C, D, E, F, H]
given `hog`: Functor[C, E, H O G] = composedFunctor[C, D, E, G, H]
def σ[Z]: D[F[Z], G[Z]] = sigma.τ
def h[Z, Y]: D[Z, Y] => E[H[Z], H[Y]] = summon[Functor[D, E, H]].f
def hσ[Z]: E[(H O F)[Z], (H O G)[Z]] = h(σ[Z])
new:
def τ[Z]: E[(H O F)[Z], (H O G)[Z]] = hσ
The generic leftFunctorComposedNaturalTransformation
implementation defines def
member τ
.
Note that leftFunctorComposedNaturalTransformation
is a def
with a natural transformation value parameter
sigma
.
package implementation.generic
import notation.{O}
import specification.{Category, Functor, NaturalTransformation}
def rightFunctorComposedNaturalTransformation[
C[-_, +_]: Category,
D[-_, +_]: Category,
E[-_, +_]: Category,
F[+_]: [_[+_]] =>> Functor[C, D, F],
H[+_]: [_[+_]] =>> Functor[D, E, H],
K[+_]: [_[+_]] =>> Functor[D, E, K]
](sigma: NaturalTransformation[D, E, H, K]): NaturalTransformation[C, E, H O F, K O F] =
given `hof`: Functor[C, E, H O F] = composedFunctor[C, D, E, F, H]
given `kof`: Functor[C, E, K O F] = composedFunctor[C, D, E, F, K]
def σ[Z]: E[H[Z], K[Z]] = sigma.τ
def σf[Z]: E[(H O F)[Z], (K O F)[Z]] = σ[F[Z]]
new:
def τ[Z]: E[(H O F)[Z], (K O F)[Z]] = σf
The generic rightFunctorComposedNaturalTransformation
implementation defines def
member τ
.
Note that rightFunctorComposedNaturalTransformation
is a def
with a natural transformation value parameter.
Let's continue with some Yoneda related implementations and extending specifications
Let
package notation
type Yoneda = [C[-_, +_]] =>> [Z] =>> [Y] =>> C[Z, Y]
in
package implementation.generic
import notation.{Yoneda}
import specification.{Category, Functor}
import implementation.specific.{functionCategory}
given yonedaFunctor[C[-_, +_]: Category, Z]: Functor[C, Function, Yoneda[C][Z]] with
type YF = [Z] =>> [Y] =>> Yoneda[C][Z][Y]
def f[Y, X]: C[Y, X] => Function[YF[Z][Y], YF[Z][X]] =
`y-->x` => `z-->y` => `y-->x` o `z-->y`
The generic yonedaFunctor
implementation for type Z
, defines def
member f
using morphism composition.
package specification
import notation.{I, Yoneda}
trait PreFunctional[C[-_, +_]] extends Functor[Function, C, I]
trait PreFunctionalCategory[C[-_, +_]] extends Category[C] with PreFunctional[C]:
def f2m[Z, Y]: Function[Z, Y] => C[Z, Y] = f
Using f2m
of the PreFunctionalCategory
specification functions of type Function[Z, Y]
can be used as
morphisms of type C[Z, Y]
.
The PreFunctionalCategory
code above is not completed yet.
Later on it will be completed.
package implementation.generic
import notation.{I, Yoneda}
import specification.{EndoFunctor, PreFunctionalCategory}
import implementation.generic.{composedFunctor}
import implementation.specific.{functionCategory}
given yonedaEndoFunctor[C[-_, +_]: PreFunctionalCategory, Z]: EndoFunctor[C, Yoneda[C][Z]] =
type YEF = [Z] =>> [Y] =>> Yoneda[C][Z][Y]
composedFunctor[C, Function, C, YEF[Z], I]
yonedaEndoFunctor
is the composition of two functors.
Later on it will be proved that the composition of two functors satisfies the functor laws.
As a consequence, yonedaEndoFunctor
satisfies the functor laws.
Let
package notation
type U = Unit
in
package specification
import notation.{I, O, U, Yoneda}
import implementation.generic.{identityEndoFunctor, yonedaFunctor, yonedaEndoFunctor}
trait PreFunctional[C[-_, +_]] extends Functor[Function, C, I] with PreTriple[C, Yoneda[C][U]]
trait PreFunctionalCategory[C[-_, +_]] extends Category[C] with PreFunctional[C]:
def f2m[Z, Y]: Function[Z, Y] => C[Z, Y] = f
given PreFunctionalCategory[C] = this
type GF = [Z] =>> C[U, Z]
def gf[Z, Y]: C[Z, Y] => C[GF[Z], GF[Y]] =
yonedaEndoFunctor[C, U].f
def v2gv[Z]: Function[Z, GF[Z]] = z => f2m(u => z)
val nu: EndoNaturalTransformation[C, I, GF] =
new:
def τ[Z]: C[I[Z], GF[Z]] = f2m(v2gv)
type YF = [Z] =>> [Y] =>> Yoneda[C][Z][Y]
def yfz[Z, Y, X]: C[Y, X] => Function[YF[Z][Y], YF[Z][X]] =
yonedaFunctor[C, Z].f
type YEF = [Z] =>> [Y] =>> Yoneda[C][Z][Y]
def yefz[Z, Y, X]: C[Y, X] => C[YEF[Z][Y], YEF[Z][X]] =
yonedaEndoFunctor[C, Z].f
The PreFunctionalCategory
is a convenient place to define other members.
type
member GF
(global functor) is defined as [Z] =>> C[U, Z]
where type
U
is defined as the one
element type Unit
.
Morphisms of type GF[Z]
, in this document referred to as global values (of type Z
), are the pointfree
versionof values (of type Z
).
def
member gf
is defined as function yonedaEndoFunctor[C, U].f
.
def
member v2gv
(value to global value) is defined as function z => f2m(u => z)
.
Using defined val
member nu
of PreTriple[C, Yoneda[C][U]]
, def
member τ: C[Z, GF[Z]]
, a morphism from
values to global values, is defined as f2m(v2gv)
.
Furthermore type
members YF
(Yoneda functor) and YEF
(Yoneda endofunctor) are defined, and corresponding
def
members yfz
and yefz
are defined as well.
Note that names yfz
and yefz
are used since Z
is not a type parameter of PreFunctionalCategory
.
Note that gf
is a special case of yef
, using type argument U
for type parameter Z
.
package specification
import notation.{I, U, Yoneda}
trait Functional[C[-_, +_]] extends Functor[Function, C, I] with Triple[C, Yoneda[C][U]]
trait FunctionalCategory[C[-_, +_]] extends PreFunctionalCategory[C] with Functional[C]
Using declared val
member mu
of Triple[C, Yoneda[C][U]]
, def
member τ: C[(GF O GF)[Z], Z], GF[Z]]
, a
morphism from global global values to global values, is declared.
Until now we have not associated any laws with specifications and proofs of laws with implementations.
This document only involves equational laws and corresponding equational proofs.
The pointful Yoneda lemma and the pointfree Yoneda lemma are both propositions.
This document only involves equational propositions and corresponding equational proofs.
package notation
type Law[Z] = Tuple2[Z, Z]
def Law[Z]: Tuple2[Z, Z] => Law[Z] = identity
type
Law
is defined as Tuple[Z, Z]
, requiring both sides of an equational law to have the same type.
def
Law
is also defined so that laws can be encoded as Law(lz, rz)
.
package notation
type Proof = [Z] =>> List[Z]
def Proof[Z](zs: Z*): List[Z] = List(zs: _*)
type
Proof
is defined as List[Z]
, requiring all steps of an equational proof to have the same type.
For the proofs of this document, especially the ones of generic laws, this provides some confidence that the
proofs are correct.
def
Proof
is also defined so that proofs can be encoded as Proof(z1, z2, z3, ...)
.
package notation
type Proposition[Z] = Tuple2[Z, Z]
def Proposition[Z]: Tuple2[Z, Z] => Proposition[Z] = identity
type
Proposition
is defined as Tuple[Z, Z]
, requiring both sides of an equational proposition to have
the same type.
def
Proposition
is also defined so that propositions can be encoded as Proposition(lz, rz)
.
The names of the laws, hopefully, explain what the laws are all about.
package specification
// ...
import notation.{Law}
class CategoryLaws[C[-_, +_]: Category]:
val category = summon[Category[C]]
def leftIdentityLaw[Z, Y]: C[Z, Y] => Law[C[Z, Y]] =
`z-->y` =>
def `y-->y`[Y]: C[Y, Y] = category.identity
Law(`y-->y` o `z-->y`, `z-->y`)
def rightIdentityLaw[Z, Y]: C[Z, Y] => Law[C[Z, Y]] =
`z-->y` =>
def `z-->z`[Z]: C[Z, Z] = category.identity
Law(`z-->y` o `z-->z`, `z-->y`)
def associativityLaw[Z, Y, X, W]: (C[Z, Y], C[Y, X], C[X, W]) => Law[C[Z, W]] =
(`z-->y`, `y-->x`, `x-->w`) => Law((`x-->w` o `y-->x`) o `z-->y`, `x-->w` o (`y-->x` o `z-->y`))
The def
local definition `y-->y`
resp. `z-->z`
are introduced to, hopefully, make the
leftIdentityLaw
resp. rightIdentityLaw
law look natural.
The infix extension
member o
, hopefully, makes the `` law look natural
package specification
// ...
import notation.{Law}
class FunctorLaws[C[-_, +_]: Category, D[-_, +_]: Category, F[+_]: [_[+_]] =>> Functor[C, D, F]]:
def identityLaw[Z]: Law[D[F[Z], F[Z]]] =
def `z-c->z`[Z]: C[Z, Z] = summon[Category[C]].identity
def `f[z]-d->f[z]`[F[Z]]: D[F[Z], F[Z]] = summon[Category[D]].identity
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
Law(f[Z, Z](`z-c->z`), `f[z]-d->f[z]`)
def compositionLaw[Z, Y, X]: (C[Z, Y], C[Y, X]) => Law[D[F[Z], F[X]]] =
(`z-->y`, `y-->x`) =>
extension [Z, Y, X](`y-->x`: C[Y, X])
def o_c(`z-->y`: C[Z, Y]): C[Z, X] = summon[Category[C]].o[Z, Y, X](`y-->x`)(`z-->y`)
extension [Z, Y, X](`y-->x`: D[Y, X])
def o_d(`z-->y`: D[Z, Y]): D[Z, X] = summon[Category[D]].o[Z, Y, X](`y-->x`)(`z-->y`)
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
Law(f[Z, X](`y-->x` o_c `z-->y`), f[Y, X](`y-->x`) o_d f[Z, Y](`z-->y`))
Local definition are introduced to, hopefully, make laws more readable.
Type parameters are introduced to, hopefully, make laws more understandable.
The Scala
type system does not warry about names, and, more important, can understand laws without most type
parameters.
def identityLaw[Z]: Law[D[F[Z], F[Z]]] =
def identity_C[Z]: C[Z, Z] = summon[Category[C]].identity
def identity_D[Z]: D[Z, Z] = summon[Category[D]].identity
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
Law(f(identity_C), identity_D)
def compositionLaw[Z, Y, X]: (C[Z, Y], C[Y, X]) => Law[D[F[Z], F[X]]] =
(`z-->y`, `y-->x`) =>
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
Law(f(`y-->x` o `z-->y`), f(`y-->x`) o f(`z-->y`))
Later on, when proving laws and propositions, the Scala
type system will turn out to be very helpful.
package specification
// ...
import notation.{Law}
class NaturalTransformationLaw[
C[-_, +_]: Category,
D[-_, +_]: Category,
F[+_]: [_[+_]] =>> Functor[C, D, F],
G[+_]: [_[+_]] =>> Functor[C, D, G],
T[_[-_, +_], _[-_, +_], _[+_], _[+_]]: [_[_[-_, +_], _[-_, +_], _[+_], _[+_]]] =>> NaturalTransformation[
C,
D,
F,
G
]
]:
def commutativityLaw[Z, Y]: C[Z, Y] => Law[D[F[Z], G[Y]]] =
`z-->y` =>
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
def g[Z, Y]: C[Z, Y] => D[G[Z], G[Y]] = summon[Functor[C, D, G]].f
extension [Z, Y, X](`y-->x`: D[Y, X])
def o_d(`z-->y`: D[Z, Y]): D[Z, X] = summon[Category[D]].o(`y-->x`)(`z-->y`)
def τ[Z]: D[F[Z], G[Z]] = summon[NaturalTransformation[C, D, F, G]].τ
(τ[Y] o_d f[Z, Y](`z-->y`), g[Z, Y](`z-->y`) o_d τ[Z])
Agreed, commutativityLaw
is, maybe, not the best name since two functors, f
and g
, are involved.
Local definition are introduced to, hopefully, make laws more readable.
Type parameters are introduced to, hopefully, make laws more understandable.
The Scala
type system does not warry about names, and, more important, can understand laws without most type
parameters.
def commutativityLaw[Z, Y]: C[Z, Y] => Law[D[F[Z], G[Y]]] =
`z-->y` =>
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
def g[Z, Y]: C[Z, Y] => D[G[Z], G[Y]] = summon[Functor[C, D, G]].f
def τ[Z]: D[F[Z], G[Z]] = summon[NaturalTransformation[C, D, F, G]].τ
Law(τ o f(`z-->y`), g(`z-->y`) o τ)
package specification
// ...
import notation.{Law}
class PreTripleLaw[C[-_, +_]: Category, PT[+_]: [_[+_]] =>> PreTriple[C, PT]]:
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[I[Z], PT[Y]]] =
`z-->y` =>
val preTriple: PreTriple[C, PT] = summon[PreTriple[C, PT]]
import preTriple.{pt, ν}
import implementation.generic.{identityEndoFunctor}
def i[Z, Y]: C[Z, Y] => C[I[Z], I[Y]] = identityEndoFunctor.f
Law(ν[Y] o i[Z, Y](`z-->y`), pt[Z, Y](`z-->y`) o ν[Z])
This law is just a special version of an already mentioned one.
Again the Scala
type system can understand the law without most type parameters.
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[I[Z], PT[Y]]] =
`z-->y` =>
val preTriple: PreTriple[C, PT] = summon[PreTriple[C, PT]]
import preTriple.{pt, ν}
import implementation.generic.{identityEndoFunctor}
def i[Z, Y]: C[Z, Y] => C[I[Z], I[Y]] = identityEndoFunctor.f
Law(ν o i(`z-->y`), pt(`z-->y`) o ν)
package specification
// ...
import notation.{Law}
class TripleLaws[C[-_, +_]: Category, T[+_]: [_[+_]] =>> Triple[C, T]]:
val category: Category[C] = summon[Category[C]]
val triple: Triple[C, T] = summon[Triple[C, T]]
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[(T O T)[Z], T[Y]]] =
`z-->y` =>
import triple.{t, μ}
import implementation.specific.{functionCategory}
Law(μ[Y] o (t[T[Z], T[Y]] o t[Z, Y])(`z-->y`), t[Z, Y](`z-->y`) o μ[Z])
def leftIdentityLaw[Z]: Law[C[T[Z], T[Z]]] =
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import triple.{ν, μ}
Law(μ[Z] o ν[T[Z]], ι[T[Z]])
def rightIdentityLaw[Z]: Law[C[T[Z], T[Z]]] =
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import triple.{t, ν, μ}
Law(μ[Z] o t[Z, T[Z]](ν[Z]), ι[T[Z]])
def associativityLaw[Z]: Law[C[(T O T O T)[Z], T[Z]]] =
import triple.{t, μ}
Law(μ[Z] o μ[T[Z]], μ[Z] o t[(T O T)[Z], T[Z]](μ[Z]))
The first law is just a special version of an already mentioned one.
Again the Scala
type system can understand the law without most type parameters.
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[(T O T)[Z], T[Y]]] =
`z-->y` =>
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import implementation.specific.{functionCategory}
Law(μ o (t o t)(`z-->y`), t(`z-->y`) o (μ))
def leftIdentityLaw[Z]: Law[C[T[Z], T[Z]]] =
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import triple.{ν, μ}
Law(μ o ν, ι)
def rightIdentityLaw[Z]: Law[C[T[Z], T[Z]]] =
import category.{ι}
import triple.{t, ν, μ}
Law(μ o t(ν), ι)
def associativityLaw[Z]: Law[C[(T O T O T)[Z], T[Z]]] =
import triple.{t, μ}
Law(μ o μ, μ o t(μ))
It is challenging to translate the laws above back to the ones that use type parameters.
Another way to encode the laws is by using leftFunctorComposedNaturalTransformation
and
righFunctorComposedNaturalTransformation
def leftIdentityLaw[Z]: Law[C[T[Z], T[Z]]] =
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import triple.{ν, μ}
import notation.{I}
import triple.{nu}
given EndoFunctor[C, T] = triple.tef
import implementation.generic.{
identityEndoFunctor,
rightFunctorComposedNaturalTransformation
}
def νt[Z]: C[T[Z], (T O T)[Z]] = rightFunctorComposedNaturalTransformation(nu).τ
Law(μ o νt, ι)
def rightIdentityLaw[Z]: Law[C[T[Z], T[Z]]] =
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import triple.{t, ν, μ}
import notation.{I}
import triple.{nu}
given EndoFunctor[C, T] = triple.tef
import implementation.generic.{
identityEndoFunctor,
leftFunctorComposedNaturalTransformation
}
def tν[Z]: C[T[Z], (T O T)[Z]] = leftFunctorComposedNaturalTransformation(nu).τ
Law(μ o tν, ι)
def associativityLaw[Z]: Law[C[(T O T O T)[Z], T[Z]]] =
import triple.{t, μ}
import notation.{I}
import triple.{mu}
given EndoFunctor[C, T] = triple.tef
import implementation.generic.{
composedFunctor,
leftFunctorComposedNaturalTransformation,
rightFunctorComposedNaturalTransformation
}
def μt[Z]: C[(T O T O T)[Z], (T O T)[Z]] = rightFunctorComposedNaturalTransformation(mu).τ
def tμ[Z]: C[(T O T O T)[Z], (T O T)[Z]] = leftFunctorComposedNaturalTransformation(mu).τ
Law(μ o μt, μ o tμ)
This way to encode the laws is in sync with writinh the laws using mathematical notation.
So what is the preferred notation for writing leftIdentityLaw
?
Law(μ[Z] o ν[T[Z]], ι[T[Z]])
Law(μ o ν, ι)
Law(μ o νt, ι)
Well ... it depends.
For documentation for human beings, perhaps the first notation.
For correspondence with mathematical documents, perhaps the last notation.
Anyway, in contrast with, often used, matematicical "abuse of notation" to simplify notation, the middle notation, which is the simplest one, is not an example of programmatic "abuse of notation". The Scala type system is happy with it (and so should human beings be).
On the one hand, human beings can not fool the Scala
system by using "abuse of notation".
On the other hand, human beings should start worrying when writing laws the Scala
type system is not happy
with.
In my opinion this is a situation were software (the Scala
system) can better deal with complexity than human
beings. Being better with dealing with (not necessarily complexity related) difficulty is another matter.
For sure, there exists software that can assist human beings to deal with difficulty, like proof assistant software, but, at least as far as I know (I may be wrong) there does not exist software that, out of the blue, comes up with a proof of, say, the pointful Yoneda lemma resp. our pointful Yoneda lemma, or comes up with the formulation of the lemmas themselves.
But, then again, never say never, ... .
package specification
// ...
import notation.{Law}
class PreFunctionalCategoryLaws[C[-_, +_]: PreFunctionalCategory]:
val preFunctionalCategory = summon[PreFunctionalCategory[C]]
import preFunctionalCategory.{GF}
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[I[Z], GF[Y]]] =
`z-->y` =>
import preFunctionalCategory.{gf, ν}
def i[Z, Y]: C[Z, Y] => C[Z, Y] = identityEndoFunctor[C].f
(ν o i(`z-->y`), gf(`z-->y`) o ν)
import preFunctionalCategory.{f2m}
import implementation.specific.{functionCategory}
def compositionLaw[Z, Y, X]
: Function[Z, Y] => (Function[Y, X] => Law[C[Z, X]]) =
`z=>y` =>
`y=>x` =>
(f2m(`y=>x` o `z=>y`), f2m(`y=>x`) o f2m(`z=>y`))
def identityLaw[Z]: Law[C[Z, Z]] =
def `z-->z`[Z]: C[Z, Z] = preFunctionalCategory.identity
def `z=>z`[Z]: Function[Z, Z] = functionCategory.identity
(f2m(`z=>z`), `z-->z`)
def nuLaw[Z]: GF[Z] => Law[(GF O GF)[Z]] =
`u-->z` =>
import preFunctionalCategory.{v2gv, ν}
(ν[Z] o `u-->z`, v2gv[C[U, Z]](`u-->z`))
The first three laws are special versions of already mentioned ones.
Again the Scala
type system can understand the fourth law with less type parameters.
def nuLaw[Z]: GF[Z] => Law[(GF O GF)[Z]] =
`u-->z` =>
import preFunctionalCategory.{v2gv, ν}
(ν o `u-->z`, v2gv(`u-->z`))
Again, it is challenging to translate the laws above back to the ones that use type parameters.
package specification
// ...
import notation.{O, Law}
import implementation.specific.{functionCategory}
class FunctionalCategoryLaws[C[-_, +_]: FunctionalCategory]:
val functionalCategory = summon[FunctionalCategory[C]]
import functionalCategory.{GF}
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[(GF O GF)[Z], GF[Y]]] =
`z-->y` =>
import functionalCategory.{gf, μ}
(μ[Y] o (gf[GF[Z], GF[Y]] o gf[Z, Y])(`z-->y`), gf[Z, Y](`z-->y`) o μ[Z])
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
def leftIdentityLaw[Z]: Law[C[GF[Z], GF[Z]]] =
import functionalCategory.{gf, ν, μ}
(μ[Z] o gf(ν[Z]), ι[GF[Z]])
def rightIdentityLaw[Z]: Law[C[GF[Z], GF[Z]]] =
import functionalCategory.{ν, μ}
(μ[Z] o ν[GF[Z]], ι[GF[Z]])
def associativityLaw[Z]: Law[C[(GF O GF O GF)[Z], GF[Z]]] =
import functionalCategory.{gf, μ}
(μ[Z] o μ[GF[Z]], μ[Z] o gf[(GF O GF)[Z], GF[Z]](μ[Z]))
The laws are special versions of already mentioned ones.
Again the Scala
type system can understand the laws with less type parameters.
def commutativityLaw[Z, Y]: C[Z, Y] => Law[C[(GF O GF)[Z], GF[Y]]] =
`z-->y` =>
import functionalCategory.{gf, μ}
(μ o (gf o gf)(`z-->y`), gf(`z-->y`) o μ)
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
def leftIdentityLaw[Z]: Law[C[GF[Z], GF[Z]]] =
import functionalCategory.{gf, ν, μ}
(μ o gf(ν), ι)
def rightIdentityLaw[Z]: Law[C[GF[Z], GF[Z]]] =
import functionalCategory.{ν, μ}
(μ o ν, ι)
def associativityLaw[Z]: Law[C[(GF O GF O GF)[Z], GF[Z]]] =
import functionalCategory.{gf, μ}
(μ o μ, μ o gf(μ))
Again, it is challenging to translate the laws above back to the ones that use type parameters.
We are done with declaring specifications and defining their laws.
Let
package notation
val u: U = ()
in
package implementation.specific
import notation.{I, O, U, u}
import specification.{FunctionalCategory, EndoNaturalTransformation}
import implementation.generic.{identityEndoFunctor, composedFunctor, yonedaEndoFunctor}
given functionFunctionalCategory: FunctionalCategory[Function] with
def composition[Z, Y, X]: (Function[Y, X], Function[Z, Y]) => Function[Z, X] = functionCategory.composition
def identity[Z]: Function[Z, Z] = functionCategory.identity
def f[Z, Y]: Function[Z, Y] => Function[I[Z], I[Y]] = identityEndoFunctor.f
override val nu: EndoNaturalTransformation[Function, I, GF] =
new:
def τ[Z]: Function[I[Z], GF[Z]] = z => u => z
val mu: EndoNaturalTransformation[Function, GF O GF, GF] =
new:
def τ[Z]: Function[(GF O GF)[Z], GF[Z]] = `u=>(u=>z)` => `u=>(u=>z)`(u)
Only def
member τ
of val
member mu
needs to be defined.
Also def
member τ
of val
member nu
is redefined (cfr. override
).
Later on the correctness of this redefinition will be proved.
We are done with defining implementations
It is time to start with proofs.
Let's start with specific implementations.
The definitions and auxiliary propositions that are used are written in comments.
package implementation.specific
// ...
import notation.{Proof}
class FunctionCategoryProofs:
def associativityProof[Z, Y, X, W]: ((Function[Z, Y], Function[Y, X], Function[X, W]) => Z => Proof[W]) =
(`z=>y`, `y=>x`, `x=>w`) =>
z =>
Proof(
((`x=>w` o `y=>x`) o `z=>y`)(z),
// definition o for function(Functional)Category
(`x=>w` o `y=>x`)(`z=>y`(z)),
// definition o for function(Functional)Category
`x=>w`(`y=>x`(`z=>y`(z))),
// definition o for function(Functional)Category
`x=>w`((`y=>x` o `z=>y`)(z)),
// definition o for functionCategory
(`x=>w` o (`y=>x` o `z=>y`))(z)
// done
)
def leftIdentityProof[Z, Y]: Function[Z, Y] => Z => List[Y] =
`z=>y` =>
z =>
def `y-->y`[Y] = functionCategory.identity[Y]
List(
(`y-->y` o `z=>y`)(z),
// definition o for functionCategory
`y-->y`(`z=>y`(z)),
// definition `y-->y` for functionCategory
(`z=>y`)(z)
// done
)
def rightIdentityProof[Z, Y]: Function[Z, Y] => Z => List[Y] =
`z=>y` =>
z =>
def `z-->z`[Z] = functionCategory.identity[Z]
List(
(`z=>y` o `z-->z`)(z),
// definition o for functionCategory
`z=>y`(`z-->z`(z)),
// definition `z-->z` for functionCategory
(`z=>y`)(z)
// done
)
The proofs are simple and easy.
package implementation.specific
// ...
import notation.{Proof}
import implementation.specific.{functionCategory}
class FunctionFunctionalCategoryProofs:
import functionFunctionalCategory.{GF, f, f2m, v2gv, ν}
def f2mPropositionProof[Z, Y]: Function[Z, Y] => Proof[Function[Z, Y]] =
`z=>y` =>
Proof(
f2m(`z=>y`),
// definition f2m
f(`z=>y`),
// definition f for function(Functional)Category
identityEndoFunctor.f(`z=>y`),
// definition f for identityEndoFunctor
`z=>y`
)
def nuPropositionProof[Z]: Proof[Function[I[Z], GF[Z]]] =
Proof(
ν,
// definition ν
f2m(v2gv),
// definition v2gv
f2m(z => f2m(u => z)),
// f2mProposition (twice)
z => u => z
)
def gfPropositionProof[Z, Y]: Function[Z, Y] => Proof[Function[GF[Z], GF[Y]]] =
import functionFunctionalCategory.{f, gf, yfz, yefz, f2m}
`z=>y` =>
Proof(
gf(`z=>y`),
// definition gf
yonedaEndoFunctor[Function, U].f(`z=>y`),
// definition yonedaEndoFunctor (via composedFunctor)
f2m(yonedaFunctor[Function, U].f(`z=>y`)),
// f2mProposition
yonedaFunctor[Function, U].f(`z=>y`),
// definition yfz
yfz(`z=>y`)
)
def nuCommutativityProof[Z, Y]: Function[Z, Y] => Proof[Function[I[Z], GF[Y]]] =
`z=>y` =>
import functionFunctionalCategory.{yfz, gf, ν}
def i[Z, Y]: Function[Z, Y] => Function[Z, Y] = identityEndoFunctor.f
Proof(
gf(`z=>y`) o ν,
// gfProposition
yfz(`z=>y`) o ν,
// definition ν for function(Pre)FunctionalCategory
yfz(`z=>y`) o (z => (u => z)),
// definition o for function(Functional)Category
z => yfz(`z=>y`)(u => z),
// definition yfz
z => `z=>y` o (u => z),
// definition o for function(Functional)Category
z => u => `z=>y`(z),
// definition definition o for function(Functional)Category (substituting `z=>y`(z) for y)
((y: Y) => (u: U) => y) o (z => `z=>y`(z)),
// lambda calculus eta conversion
((y: Y) => ((u: U) => y)) o `z=>y`,
// definition ν
ν o `z=>y`,
// definition identityEndoFunctor.f
ν o identityEndoFunctor.f(`z=>y`),
// definition i
ν o i(`z=>y`)
// done
)
def muCommutativityProof[Z, Y]: Function[Z, Y] => Proof[(GF O GF)[Z] => GF[Y]] =
`z=>y` =>
import functionFunctionalCategory.{yfz, gf, μ}
Proof(
gf(`z=>y`) o μ,
// gfProposition
yfz(`z=>y`) o μ,
// definition μ for functionFunctionalCategory
yfz(`z=>y`) o (`u=>(u=>z)` => `u=>(u=>z)`(u)),
// definition o for function(Functional)Category
`u=>(u=>z)` => yfz(`z=>y`)(`u=>(u=>z)`(u)),
// definition yfz
`u=>(u=>z)` => (`z=>y` o `u=>(u=>z)`(u)),
// eta conversion lambda calculus
(`u=>(u=>z)` => ((u: U) => (`z=>y` o `u=>(u=>z)`(u)))(u)),
// definition o for function(Functional)Category
((`u=>(u=>y)`: (GF O GF)[Y]) => `u=>(u=>y)`(u)) o (`u=>(u=>z)` => (u => `z=>y` o `u=>(u=>z)`(u))),
// definition o for function(Functional)Category
((`u=>(u=>y)`: (GF O GF)[Y]) => `u=>(u=>y)`(u)) o (`u=>(u=>z)` =>
((`u=>z`: GF[Z]) => `z=>y` o `u=>z`) o (u => `u=>(u=>z)`(u))
),
// eta conversion lambda calculus
(((`u=>(u=>y)`: (GF O GF)[Y]) => `u=>(u=>y)`(u)) o (`u=>(u=>z)` =>
((`u=>z`: GF[Z]) => `z=>y` o `u=>z`) o `u=>(u=>z)`
)),
// definition yfz
(((`u=>(u=>y)`: (GF O GF)[Y]) => `u=>(u=>y)`(u)) o yfz((`u=>z`: GF[Z]) => `z=>y` o `u=>z`)),
// definition yfz
(((`u=>(u=>y)`: (GF O GF)[Y]) => `u=>(u=>y)`(u)) o yfz(yfz(`z=>y`))),
// definition μ for functionFunctionalCategory
μ o yfz(yfz(`z=>y`)),
// gfProposition twice
μ o gf(gf(`z=>y`))
// done
)
def nuProof[Z]: GF[Z] => Proof[(GF O GF)[Z]] =
`u=>z` =>
import functionFunctionalCategory.{f2m, v2gv, ν}
Proof(
ν o `u=>z`,
// definition ν for function(Pre)FunctionalCategory
((z: Z) => ((u: U) => z)) o `u=>z`,
// eta conversion lambda calculus
(((z: Z) => ((u: U) => z)) o (u => `u=>z`(u))),
// definition o for function(Functional)Category
u => (u => `u=>z`(u)),
// eta conversion lambda calculus
u => `u=>z`,
// f2mProposition
f2m(u => `u=>z`),
// definition v2gv
v2gv(`u=>z`)
// done
)
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: Function[Z, Z] = identityEndoNaturalTransformation.τ
def leftIdentityProof[Z]: Proof[GF[Z] => GF[Z]] =
import functionFunctionalCategory.{f2m, yfz, yefz, gf, ν, μ}
Proof(
μ o gf(ν),
// gfProposition
μ o yfz(ν),
// definition ν for function(Pre)FunctionalCategory
μ o yfz(f2m(z => u => z)),
// f2mProposition
μ o yfz(z => u => z),
// definition μ for functionFunctionalCategory
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o yfz(z => u => z),
// definition yfz
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o (`u=>z` => ((z: Z) => ((u: U) => z)) o `u=>z`),
// definition o for function(Functional)Category
`u=>z` => (((z: Z) => ((u: U) => z)) o `u=>z`)(u),
// lambda calculus ν conversion
`u=>z` => (((z: Z) => ((u: U) => z)) o (u => `u=>z`(u)))(u),
// definition o for function(Functional)Category
`u=>z` => (u => `u=>z`(u)),
// lambda calculus ν conversion
(`u=>z` => `u=>z`),
// definition ι for function(Functional)Category
ι
// done
)
def rightIdentityProof[Z]: Proof[GF[Z] => GF[Z]] =
import functionFunctionalCategory.{ν, μ}
Proof(
μ o ν,
// definition ν for function(Pre)FunctionalCategory
μ o (`u=>z` => (u => `u=>z`)),
// definition μ for functionFunctionalCategory
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o (`u=>z` => (_ => `u=>z`)),
// definition o for function(Functional)Category
`u=>z` => ((u: U) => `u=>z`)(u),
// eta conversion lambda calculus
`u=>z` => `u=>z`,
// definition ι for function(Functional)Category
ι
// done
)
def associativityProof[Z]: Proof[(GF O GF O GF)[Z] => GF[Z]] =
import functionFunctionalCategory.{f2m, yfz, yefz, gf, μ}
Proof(
μ o μ,
// definition μ for functionFunctionalCategory (twice)
(((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o ((`u=>(u=>(u=>z))`: (GF O GF O GF)[Z]) =>
`u=>(u=>(u=>z))`(u)
)),
// definition o for function(Functional)Category
(`u=>(u=>(u=>z))`: (GF O GF O GF)[Z]) => `u=>(u=>(u=>z))`(u)(u),
// definition o for function(Functional)Category
(`u=>(u=>(u=>z))`: (GF O GF O GF)[Z]) =>
(((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o `u=>(u=>(u=>z))`)(u),
// definition o for function(Functional)Category
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o (`u=>(u=>(u=>z))` =>
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o `u=>(u=>(u=>z))`
),
// definition yfz
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o yfz(`u=>(u=>z)` => `u=>(u=>z)`(u)),
// definition gfProposition
((`u=>(u=>z)`: (GF O GF)[Z]) => `u=>(u=>z)`(u)) o gf(`u=>(u=>z)` => `u=>(u=>z)`(u)),
// definition μ for functionFunctionalCategory
μ o gf(μ)
// done
)
The proofs are more complex but they are still easy.
We are done with proofs of specific implementations.
Let's continue with proofs of generic implementations.
The definitions, laws and propositions that are used are written in comments.
package implementation.generic
// ...
import notation.{Proof}
class IdentityEndoFunctorProofs[C[-_, +_]: Category]:
def i[Z, Y]: C[Z, Y] => C[Z, Y] = identityEndoFunctor.f
def identityProof[Z]: Proof[C[I[Z], I[Z]]] =
def `z-->z`: C[Z, Z] = summon[Category[C]].identity
def `i[z]-->i[z]` : C[I[Z], I[Z]] = summon[Category[C]].identity
List(
i(`z-->z`),
// definition i
`z-->z`,
// definition `i[z]-->i[z]`
`i[z]-->i[z]`
// done
)
def compositionProof[Z, Y, X]: C[Z, Y] => C[Y, X] => Proof[C[I[Z], I[X]]] =
`z-->y` =>
`y-->x` =>
Proof(
i(`y-->x` o `z-->y`),
// definition i
`y-->x` o `z-->y`,
// definition i (twice)
i(`y-->x`) o i(`z-->y`)
// done
)
package implementation.generic
// ...
import notation.{Proof}
class ComposedFunctorProofs[
C[-_, +_]: Category,
D[-_, +_]: Category,
E[-_, +_]: Category,
F[+_]: [_[+_]] =>> Functor[C, D, F],
G[+_]: [_[+_]] =>> Functor[D, E, G]
]:
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
def g[Z, Y]: D[F[Z], F[Y]] => E[(G O F)[Z], (G O F)[Y]] = summon[Functor[D, E, G]].f
def gof[Z, Y]: C[Z, Y] => E[(G O F)[Z], (G O F)[Y]]= composedFunctor[C, D, E, F, G].f
def identityProof[Z]: Proof[E[(G O F)[Z], (G O F)[Z]]] =
val `z-c->z`: C[Z, Z] = summon[Category[C]].identity[Z]
val `f[z]-d->f[z]` : D[F[Z], F[Z]] = summon[Category[D]].identity[F[Z]]
val `(gof)[z]-e->(gof)[z]` : E[(G O F)[Z], (G O F)[Z]] = summon[Category[E]].identity[(G O F)[Z]]
Proof(
gof(`z-c->z`),
// definition f for gof
g(f(`z-c->z`)),
// identityLaw for f
g(`f[z]-d->f[z]`),
// identityLaw for g
`(gof)[z]-e->(gof)[z]`
// done
)
def compositionProof[Z, Y, X]: C[Z, Y] => (C[Y, X] => Proof[E[(G O F)[Z], (G O F)[X]]]) =
`z-->y` =>
`y-->x` =>
extension [Z, Y, X](`y-->x`: C[Y, X])
def o_c(`z-->y`: C[Z, Y]): C[Z, X] =
summon[Category[C]].o[Z, Y, X](`y-->x`)(`z-->y`)
extension [Z, Y, X](`y-->x`: D[Y, X])
def o_d(`z-->y`: D[Z, Y]): D[Z, X] =
summon[Category[D]].o[Z, Y, X](`y-->x`)(`z-->y`)
extension [Z, Y, X](`y-->x`: E[Y, X])
def o_e(`z-->y`: E[Z, Y]): E[Z, X] =
summon[Category[E]].o[Z, Y, X](`y-->x`)(`z-->y`)
Proof(
gof(`y-->x` o_c `z-->y`),
// definition f for gof
g(f(`y-->x` o_c `z-->y`)),
// compositionLaw for f
g(f(`y-->x`) o_d f(`z-->y`)),
// compositionLaw for g
g(f(`y-->x`)) o_e g(f(`z-->y`)),
// definition f for gof
gof(`y-->x`) o_e gof(`z-->y`)
// done
)
package implementation.generic
// ...
import notation.{Proof}
class IdentityNaturalTransformationProof[C[-_, +_]: Category]:
val category = summon[Category[C]]
import category.{identity}
def i[Z, Y]: C[Z, Y] => C[I[Z], I[Y]] = identityEndoFunctor.f
def ι[Z]: C[I[Z], I[Z]] = identityEndoNaturalTransformation.τ
def commutativityProof[Z, Y]: C[Z, Y] => Proof[C[I[Z], I[Y]]] =
`z-->y` =>
Proof(
ι o i(`z-->y`),
// definition i
ι o i(`z-->y`),
// definition ι
identity o `z-->y`,
// left identity law
`z-->y`,
// right identity law
`z-->y` o identity,
// definition ι
`z-->y` o ι,
// definition i
i(`z-->y`) o ι
)
package implementation.generic
// ...
import notation.{Proof}
class ComposedEndoNaturalTransformationProof[
C[-_, +_]: Category,
F[+_]: [_[+_]] =>> EndoFunctor[C, F],
G[+_]: [_[+_]] =>> EndoFunctor[C, G],
H[+_]: [_[+_]] =>> EndoFunctor[C, H],
S[_[-_, +_], _[-_, +_], _[+_], _[+_]]: [_[
_[-_, +_],
_[-_, +_],
_[+_],
_[+_]
]] =>> EndoNaturalTransformation[
C,
F,
G
],
T[_[-_, +_], _[-_, +_], _[+_], _[+_]]: [_[
_[-_, +_],
_[-_, +_],
_[+_],
_[+_]
]] =>> EndoNaturalTransformation[
C,
G,
H
]
]:
def commutativityProof[Z, Y]: C[Z, Y] => Proof[C[F[Z], H[Y]]] =
def σ[Z]: C[F[Z], G[Z]] = summon[EndoNaturalTransformation[C, F, G]].τ
def τ[Z]: C[G[Z], H[Z]] = summon[EndoNaturalTransformation[C, G, H]].τ
def τoσ[Z]: C[F[Z], H[Z]] = composedNaturalTransformation[C, F, G, H, S, T].τ
def f[Z, Y]: C[Z, Y] => C[F[Z], F[Y]] = summon[EndoFunctor[C, F]].f
def g[Z, Y]: C[Z, Y] => C[G[Z], G[Y]] = summon[EndoFunctor[C, G]].f
def h[Z, Y]: C[Z, Y] => C[H[Z], H[Y]] = summon[EndoFunctor[C, H]].f
`z-->y` =>
Proof(
τoσ o f(`z-->y`),
// definition τ for composedNaturalTransformation
(τ o σ) o f(`z-->y`),
// associativityLaw for C
τ o (σ o f(`z-->y`)),
// commutativityLaw for S
τ o (g(`z-->y`) o σ),
// categoryCompositionAssociativityLaw for C
(τ o g(`z-->y`)) o σ,
// commutativityLaw for T
(h(`z-->y`) o τ) o σ,
// associativityLaw for C
h(`z-->y`) o (τ o σ),
// definition τ for composedNaturalTransformation
h(`z-->y`) o τoσ
// done
)
Part 14: LeftFunctorComposedNaturalTransformationProof
and RightFunctorComposedNaturalTransformationProof
proof
package implementation.generic
// ...
import notation.{Proof}
import implementation.specific.{functionCategory}
class LeftFunctorComposedNaturalTransformationProof[
C[-_, +_]: Category,
D[-_, +_]: Category,
E[-_, +_]: Category,
H[+_]: [_[+_]] =>> Functor[D, E, H],
F[+_]: [_[+_]] =>> Functor[C, D, F],
G[+_]: [_[+_]] =>> Functor[C, D, G]
]:
def commutativityProof[Z, Y](
sigma: NaturalTransformation[C, D, F, G]
): C[Z, Y] => Proof[E[(H O F)[Z], (H O G)[Y]]] =
`z-->y` =>
def σ[Z]: D[F[Z], G[Z]] = sigma.τ
def h[Z, Y]: D[Z, Y] => E[H[Z], H[Y]] = summon[Functor[D, E, H]].f
def hσ[Z, Y]: E[(H O F)[Z], (H O G)[Z]] =
leftFunctorComposedNaturalTransformation[C, D, E, H, F, G](sigma).τ
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
def g[Z, Y]: C[Z, Y] => D[G[Z], G[Y]] = summon[Functor[C, D, G]].f
import implementation.specific.{functionCategory}
Proof(
hσ o (h o f)(`z-->y`),
// definition τ for leftFunctorComposedNaturalTransformation
h(σ) o (h o f)(`z-->y`),
// definition o for functionCategory
h(σ) o h(f(`z-->y`)),
// compositionLaw for h
h(σ o f(`z-->y`)),
// commutativityLaw for S
h(g(`z-->y`) o σ),
// compositionLaw for h
h(g(`z-->y`)) o h(σ),
// definition o for functionCategory
(h o g)(`z-->y`) o h(σ),
// definition τ for leftFunctorComposedNaturalTransformation
(h o g)(`z-->y`) o hσ
// done
)
package implementation.generic
// ...
import notation.{Proof}
class RightFunctorComposedNaturalTransformationProof[
C[-_, +_]: Category,
D[-_, +_]: Category,
E[-_, +_]: Category,
F[+_]: [_[+_]] =>> Functor[C, D, F],
H[+_]: [_[+_]] =>> Functor[D, E, H],
K[+_]: [_[+_]] =>> Functor[D, E, K]
]::
def commutativityProof[Z, Y](
sigma: NaturalTransformation[D, E, H, K]
): C[Z, Y] => Proof[E[(H O F)[Z], (K O F)[Y]]] =
`z-->y` =>
def σ[Z]: E[H[Z], K[Z]] = sigma.τ
def σf[Z]: E[(H O F)[Z], (K O F)[Z]] =
rightFunctorComposedNaturalTransformation[C, D, E, F, H, K](sigma).τ
def f[Z, Y]: C[Z, Y] => D[F[Z], F[Y]] = summon[Functor[C, D, F]].f
def h[Z, Y]: D[Z, Y] => E[H[Z], H[Y]] = summon[Functor[D, E, H]].f
def k[Z, Y]: D[Z, Y] => E[K[Z], K[Y]] = summon[Functor[D, E, K]].f
import implementation.specific.{functionCategory}
Proof(
σf o (h o f)(`z-->y`),
// definition τ for rightFunctorComposedNaturalTransformation
σ[F[Y]] o (h o f)(`z-->y`),
// definition o for functionCategory
σ[F[Y]] o h(f(`z-->y`)),
// commutativityLaw for S (with f(`z-->y`))
k(f(`z-->y`)) o σ[F[Z]],
// definition o for functionCategory
(k o f)(`z-->y`) o σ[F[Z]],
// definition τ for rightFunctorComposedNaturalTransformation
(k o f)(`z-->y`) o σf
// done
)
package proposition
import notation.{Yoneda, Law}
import specification.{Category, Functor, NaturalTransformation}
import implementation.specific.{functionCategory}
import implementation.generic.{yonedaFunctor}
class YonedaLemma[C[-_, +_]: Category, Z, G[+_]: [_[+_]] =>> Functor[C, Function, G]]:
val category = summon[Category[C]]
import category.{identity}
def g[Z, Y]: C[Z, Y] => Function[G[Z], G[Y]] = summon[Functor[C, Function, G]].f
type YFZ = [Y] =>> Yoneda[C][Z][Y]
def yonedaLemma1[Y]: (NaturalTransformation[C, Function, YFZ, G] => Law[Function[YFZ[Y], G[Y]]]) =
yfz2g =>
def `z-->z`[Z]: C[Z, Z] = identity[Z]
def τ[Y]: Function[YFZ[Y], G[Y]] = yfz2g.τ
def `g[z]` : G[Z] = τ(`z-->z`)
val yfz_2_g: NaturalTransformation[C, Function, YFZ, G] =
new:
def τ[Y]: Function[YFZ[Y], G[Y]] = `z-->y` => g(`z-->y`)(`g[z]`)
def σ[Y]: Function[YFZ[Y], G[Y]] = yfz_2_g.τ
Law(τ, σ)
def yonedaLemma2[Y, X]: G[Z] => (C[Y, X] => (Law[Function[YFZ[Y], G[X]]])) =
`g[z]` =>
val yfz2g: NaturalTransformation[C, Function, YFZ, G] =
new:
def τ[Y]: Function[YFZ[Y], G[Y]] = `z-->y` => g(`z-->y`)(`g[z]`)
def σ[Y]: Function[YFZ[Y], G[Y]] = yfz2g.τ
def yfz: C[Y, X] => Function[YFZ[Y], YFZ[X]] = yonedaFunctor.f
`y-->x` => Law(g(`y-->x`) o σ, σ o yfz(`y-->x`))
Natural transformations of type NaturalTransformation[C, Function, YFZ, G]
are in one-to-one correspondence
with values of type G[Z]
.
With every natural transformation with morphism τ
of type Function[YFZ[Y], G[Y]]
corresponds a value
`g[z]` = τ(`z-->z`)
of type G[Z]
and with every value `g[z]`
of type G[Z]
corresponds a
natural transformation with morphism σ = `z-->y` => g(`z-->y`)(`g[z]`)
of type Function[YFZ[Y], G[Y]]
such that τ
is equal to σ
.
Let
import notation.{I}
import specification.{EndoFunctor}
import implementation.generic.{identityEndoFunctor}
class YonedaLemmaForIdentity[Z]extends YonedaLemma[Function, Z, I]
If G
is I
, then, with every natural transformation with morphism τ
of type Function[YFZ[Y], Y]
corresponds a value z = τ(`z-->z`)
of type Z
and with every value z
of type Z
corresponds a
natural transformation with morphism σ = `z-->y` => `z-->y`(z)
of type Function[YFZ[Y], Y]
such that τ
is equal to σ
.
package proposition
// ...
import notation.{Proof}
class YonedaLemmaProof[Z, C[-_, +_]: Category, G[+_]: [_[+_]] =>> Functor[C, Function, G]]:
val category = summon[Category[C]]
import category.{identity}
def g[Z, Y]: C[Z, Y] => Function[G[Z], G[Y]] = summon[Functor[C, Function, G]].f
type YFZ = [Y] =>> Yoneda[C][Z][Y]
def yfz[Y, X]: C[Y, X] => Function[YFZ[Y], YFZ[X]] = yonedaFunctor.f
def yonedaLemma1Proof[Y]: NaturalTransformation[C, Function, YFZ, G] => (YFZ[Y] => Proof[G[Y]]) =
yfz2g =>
def `z-->z`[Z]: C[Z, Z] = identity[Z]
def τ[Y]: Function[YFZ[Y], G[Y]] = yfz2g.τ
def `g[z]` : G[Z] = τ(`z-->z`)
val yfz_2_g: NaturalTransformation[C, Function, YFZ, G] =
new:
def τ[Y]: Function[YFZ[Y], G[Y]] = `z-->y` => g(`z-->y`)(`g[z]`)
def σ[Y]: Function[YFZ[Y], G[Y]] = yfz_2_g.τ
`z-->y` =>
List(
τ(`z-->y`),
// rightIdentityLaw for C
τ(`z-->y` o `z-->z`),
// definition yfz
τ(yfz(`z-->y`)(`z-->z`)),
// definition o for functionCategory
(τ o yfz(`z-->y`))(`z-->z`),
// commutativityLaw for τ
(g(`z-->y`) o τ)(`z-->z`),
// definition o for functionCategory
g(`z-->y`)(τ(`z-->z`)),
// definition `g[z]`
g(`z-->y`)(`g[z]`),
// definition σ
σ(`z-->y`)
// done
)
def yonedaLemma2Proof[Y, X]: G[Z] => (C[Y, X] => (YFZ[Y] => Proof[G[X]])) =
`g[z]` =>
`y-->x` =>
val yfz2g: NaturalTransformation[C, Function, YFZ, G] =
new:
def τ[Y]: Function[YFZ[Y], G[Y]] = `z-->y` => g(`z-->y`)(`g[z]`)
def σ[Y]: Function[YFZ[Y], G[Y]] = yfz2g.τ
`z-->y` =>
Proof(
(g(`y-->x`) o σ)(`z-->y`),
// definition o for functionCategory
g(`y-->x`)(σ(`z-->y`)),
// definition σ
g(`y-->x`)(g(`z-->y`)(`g[z]`)),
// definition o for functionCategory
(g(`y-->x`) o g(`z-->y`))(`g[z]`),
// compositionLaw for g
(g(`y-->x` o `z-->y`))(`g[z]`),
// definition yfz
(g(yfz(`y-->x`)(`z-->y`)))(`g[z]`),
// definition σ with yfz(`y-->x`)(`z-->y`)
σ(yfz(`y-->x`)(`z-->y`)),
// definition o for functionCategory
(σ o yfz(`y-->x`))(`z-->y`)
// done
)
package proposition
import notation.{Proposition}
import specification.{PreFunctionalCategory}
class PreFunctionalCategoryPropositions[C[-_, +_]: PreFunctionalCategory]:
val preFunctionalCategory = summon[PreFunctionalCategory[C]]
import preFunctionalCategory.{GF}
def pointfreeApplication[Z, Y]: Function[Z, Y] => Z => Proposition[GF[Y]] =
`z=>y` =>
z =>
import preFunctionalCategory.{v2gv, f2m}
(f2m(`z=>y`) o v2gv(z), v2gv(`z=>y`(z)))
def pointfreeYoneda[Z, Y, X]: C[Z, Y] => C[Y, X] => Proposition[GF[C[Z, X]]] =
`z-->y` =>
`y-->x` =>
import preFunctionalCategory.{v2gv, yefz}
(yefz(`y-->x`) o v2gv(`z-->y`), v2gv(`y-->x` o `z-->y`))
pointfreeApplication
and pointfreeYoneda
are pointfree versions of function application and the Yoneda
endofunctor.
package proposition
// ...
import implementation.specific.{functionCategory}
import notation.{Proof}
class PreFunctionalCategoryProofs[Z, C[-_, +_]: PreFunctionalCategory]:
val preFunctionalCategory = summon[PreFunctionalCategory[C]]
import preFunctionalCategory.{GF}
def pointfreeApplicationProof[Z, Y]: Function[Z, Y] => Z => Proof[GF[Y]] =
`z=>y` =>
z =>
import preFunctionalCategory.{f2m, v2gv}
Proof(
f2m(`z=>y`) o v2gv(z),
// definition v2gv
f2m(`z=>y`) o f2m(u => z),
// compositionLaw for f2m
f2m(`z=>y` o (u => z)),
// definition o for functionCategory
f2m(u => `z=>y`(z)),
// definition v2gv
v2gv(`z=>y`(z))
// done
)
def pointfreeYonedaProof[Y, X]: C[Z, Y] => C[Y, X] => Proof[GF[C[Z, X]]] =
`z-->y` =>
`y-->x` =>
import preFunctionalCategory.{f2m, yfz, yefz, v2gv}
Proof(
yefz(`y-->x`) o v2gv(`z-->y`),
// definition yefz
f2m(yfz(`y-->x`)) o v2gv(`z-->y`),
// definition yfz
f2m[C[Z, Y], C[Z, X]](`z-->y` => `y-->x` o `z-->y`) o v2gv(`z-->y`),
// pointfreeApplication
v2gv(`y-->x` o `z-->y`)
// done
)
package proposition
import notation.{I, O, Proposition}
import specification.{EndoNaturalTransformation, FunctionalCategory}
class FunctionalCategoryProposition[Z, C[-_, +_]: FunctionalCategory]:
val functionalCategory = summon[FunctionalCategory[C]]
import functionalCategory.{GF}
def muProposition[Z]: (GF O GF)[Z] => Proposition[(GF O GF)[Z]] =
`u-->(u-->z)` =>
import functionalCategory.{v2gv, μ}
(μ o v2gv(`u-->(u-->z)`), `u-->(u-->z)`)
import notation.{Proof}
class FunctionalCategoryProof[Z, C[-_, +_]: FunctionalCategory]:
val functionalCategory = summon[FunctionalCategory[C]]
import functionalCategory.{GF}
def muProof[Z]: (GF O GF)[Z] => Proof[(GF O GF)[Z]] =
`u-->(u-->z)` =>
import implementation.generic.{identityEndoNaturalTransformation}
def ι[Z]: C[Z, Z] = identityEndoNaturalTransformation.τ
import functionalCategory.{v2gv, ν, μ}
Proof(
μ o v2gv(`u-->(u-->z)`),
// nuLaw for GF
μ o (ν o `u-->(u-->z)`),
// associativityLaw for C
(μ o ν) o `u-->(u-->z)`,
// leftIdentityLaw for GF
`u-->(u-->z)` o ι,
// rightIdentityLaw for C
`u-->(u-->z)`
)
Note that muProof
uses nuLaw
.
package proposition
import notation.{O, U, Law, Yoneda}
import specification.{PreFunctionalCategory, EndoFunctor, EndoNaturalTransformation}
import implementation.generic.{composedFunctor, yonedaEndoFunctor}
import implementation.specific.{functionCategory}
class PreEndoYonedaLemma[C[-_, +_]: PreFunctionalCategory, Z, G[+_]: [G[+_]] =>> EndoFunctor[C, G]]:
val preFunctionalCategory = summon[PreFunctionalCategory[C]]
import preFunctionalCategory.{GF}
type YEFZ = [Y] =>> Yoneda[C][Z][Y]
def g[Z, Y]: C[Z, Y] => C[G[Z], G[Y]] = summon[EndoFunctor[C, G]].f
def preEndoYonedaLemma1[Y]: EndoNaturalTransformation[C, YEFZ, G] => Law[C[YEFZ[Y], (GF O G)[Y]]] =
yefz2g =>
import preFunctionalCategory.{identity, f2m, v2gv, ν}
def `z-->z`[Z]: C[Z, Z] = identity
def τ[Y]: C[YEFZ[Y], G[Y]] = yefz2g.τ
def `u-->g[z]` : (GF O G)[Z] = τ o v2gv(`z-->z`)
val yefz_2_gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = f2m(`z-->y` => g(`z-->y`) o `u-->g[z]`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz_2_gfog.τ
Law(ν o τ, σ)
def preEndoYonedaLemma2[Y, X]: (GF O G)[Z] => (C[Y, X] => Law[C[YEFZ[Y], (GF O G)[X]]]) =
`u-->g[z]` =>
`y-->x` =>
import preFunctionalCategory.{f2m, yefz, gf}
val yefz2gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = f2m(`z-->y` => g(`z-->y`) o `u-->g[z]`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz2gfog.τ
Law((gf o g)(`y-->x`) o σ, σ o yefz(`y-->x`))
With every endo natural transformation with morphism τ
of type C[YEFZ[Y], G[Y]]
corresponds a global value
`u-->g[z]` = τ o v2gv(`z-->z`)
of type (GF O G)[Z]
and with every global value `u-->g[z]`
of type
(GF O G)[Z]
corresponds an endo natural transformation with morphism
σ = f2m(`z-->y` => g(`z-->y`) o `u-->g[z]`)
of type C[YEFZ[Y], (GF O G)[Y]]
such that ν o τ
is equal
to σ
.
Let
import notation.{I}
import implementation.generic.identityEndoFunctor
class PreEndoYonedaLemmaForIdentity[C[-_, +_]: PreFunctionalCategory, Z] extends PreEndoYonedaLemma[C, Z, I]
If G
is I
, then with every endo natural transformation with morphism τ
of type C[YEFZ[Y], Y]
corresponds a global value `u-->z` = τ o v2gv(`z-->z`)
of type GF[Z]
and with every global value
`u-->z`
of type GF[Z]
corresponds an endo natural transformation with morphism
σ = f2m(`z-->y` => `z-->y` o `u-->z`)
of type C[YEFZ[Y], GF[Y]]
such that ν o τ
is equal to σ
.
import notation.{Proof}
class PreEndoYonedaProof[C[-_, +_]: PreFunctionalCategory, Z, G[+_]: [G[+_]] =>> EndoFunctor[C, G]]:
val preFunctionalCategory = summon[PreFunctionalCategory[C]]
import preFunctionalCategory.{GF, gf}
type YEFZ = [Y] =>> Yoneda[C][Z][Y]
def g[Z, Y]: C[Z, Y] => C[G[Z], G[Y]] = summon[EndoFunctor[C, G]].f
def preEndoYonedaProof1[Y]: EndoNaturalTransformation[C, YEFZ, G] => (C[Z, Y] => Proof[(GF O GF O G)[Y]]) =
yefz2gfog =>
import preFunctionalCategory.{identity, f2m, v2gv, yefz, ν}
def `z-->z`[Z]: C[Z, Z] = identity
def τ[Y]: C[YEFZ[Y], G[Y]] = yefz2gfog.τ
def `u-->g[z]` : (GF O G)[Z] = τ o v2gv(`z-->z`)
val yefz_2_gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = f2m(`z-->y` => g(`z-->y`) o `u-->g[z]`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz_2_gfog.τ
`z-->y` =>
Proof(
ν[G[Y]] o σ o v2gv(`z-->y`),
// rightIdentityLaw for C
ν o σ o v2gv(`z-->y` o `z-->z`),
// pointfreeYoneda
ν o σ o yefz(`z-->y`) o v2gv(`z-->z`),
// commutativityLaw for ν o τ
(gf o g)(`z-->y`) o ν o τ o v2gv(`z-->z`),
// definition `u-->g[z]`
(gf o g)(`z-->y`) o ν o `u-->g[z]`,
// muLaw
(gf o g)(`z-->y`) o v2gv(`u-->g[z]`),
// definition o for function(Functional)Category
gf(g(`z-->y`)) o v2gv(`u-->g[z]`),
// definition gf
yefz[U, G[Z], G[Y]](g(`z-->y`)) o v2gv(`u-->g[z]`),
// pointfreeYoneda
v2gv(g(`z-->y`) o `u-->g[z]`),
// pointfreeApplication
f2m[C[Z, Y], (GF O G)[Y]](`z-->y` => g(`z-->y`) o `u-->g[z]`) o v2gv(`z-->y`),
// definition σ
(σ o v2gv(`z-->y`))
// done
)
def preEndoYonedaProof2[Y, X]: (GF O G)[Z] => (C[Y, X] => (C[Z, Y] => Proof[(GF O GF O G)[X]])) =
`u-->g[z]` =>
`y-->x` =>
import preFunctionalCategory.{f2m, v2gv, yefz, gf}
val yefz2gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = f2m(`z-->y` => g(`z-->y`) o `u-->g[z]`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz2gfog.τ
`z-->y` =>
Proof(
(gf o g)(`y-->x`) o σ o v2gv(`z-->y`),
// definition σ
(gf o g)(`y-->x`) o f2m[C[Z, Y], (GF O G)[Y]](`z-->y` => g(`z-->y`) o `u-->g[z]`) o v2gv(`z-->y`),
// pointfreeApplication
(gf o g)(`y-->x`) o v2gv(g(`z-->y`) o `u-->g[z]`),
// definition o for function(Functional)Category
gf(g(`y-->x`)) o v2gv(g(`z-->y`) o `u-->g[z]`),
// defintion gf
yefz[U, G[Y], G[X]](g(`y-->x`)) o v2gv(g(`z-->y`) o `u-->g[z]`),
// pointfreeYoneda
v2gv(g(`y-->x`) o g(`z-->y`) o `u-->g[z]`),
// compositionLaw for g
v2gv(g(`y-->x` o `z-->y`) o `u-->g[z]`),
// pointfreeApplication
f2m[C[Z, X], (GF O G)[X]](`z-->x` => g(`z-->x`) o `u-->g[z]`) o v2gv(`y-->x` o `z-->y`),
// definition σ
σ o v2gv(`y-->x` o `z-->y`),
// pointfreeYoneda
σ o yefz(`y-->x`) o v2gv(`z-->y`)
// done
)
package proposition
import notation.{O, U, Law}
import specification.{FunctionalCategory, EndoFunctor, EndoNaturalTransformation}
import implementation.generic.{composedFunctor, yonedaEndoFunctor}
import implementation.specific.{functionCategory}
class EndoYonedaLemma[C[-_, +_]: FunctionalCategory, Z, G[+_]: [G[+_]] =>> EndoFunctor[C, G]]:
val functionalCategory = summon[FunctionalCategory[C]]
import functionalCategory.{GF}
import notation.{Yoneda}
type YEFZ = [Y] =>> Yoneda[C][Z][Y]
def g[Z, Y]: C[Z, Y] => C[G[Z], G[Y]] = summon[EndoFunctor[C, G]].f
def endoYonedaLemma1[Y]: EndoNaturalTransformation[C, YEFZ, GF O G] => Law[C[YEFZ[Y], (GF O G)[Y]]] =
yefz2gfog =>
import functionalCategory.{identity, v2gv, yefz, gf, μ}
def `z-->z`: C[Z, Z] = identity
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz2gfog.τ
val `u-->(u-->g[z])` : (GF O GF O G)[Z] = τ o v2gv(`z-->z`)
val yefz_2_gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] =
μ o functionalCategory.f2m(`z-->y` => (gf o g)(`z-->y`) o `u-->(u-->g[z])`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz_2_gfog.τ
Law(τ, σ)
def endoYonedaLemma2[Y, X]: (GF O GF O G)[Z] => (C[Y, X] => Law[C[YEFZ[Y], (GF O G)[X]]]) =
`u-->(u-->g[z])` =>
`y-->x` =>
import functionalCategory.{yefz, f2m, gf, μ}
type YEFZ = [Y] =>> Yoneda[C][Z][Y]
val yefz2gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] =
μ o f2m(`z-->y` => (gf o g)(`z-->y`) o `u-->(u-->g[z])`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz2gfog.τ
Law((gf o g)(`y-->x`) o σ, σ o yefz(`y-->x`))
Endo natural transformations of type EndoNaturalTransformation[C, Function, YFZ, GF O G]
are in one-to-one
correspondence with global global values of type (GF O GF O G)[Z]
.
With every endo natural transformation with morphism τ
of type C[YEFZ[Y], (GF O G)[Y]]
corresponds a global
global value `u-->(u-->g[z])` = τ o v2gv(`z-->z`)
of type (GF O GF O G)[Z]
and with every global global
value `u-->(u-->g[z])`
of type (GF O GF OG)[Z]
corresponds an endo natural transformation with morphism
σ = μ o functionalCategory.f2m(`z-->y` => (gf o g)(`z-->y`) o `u-->(u-->g[z])`)
of type
C[YEFZ[Y], (GF O G)[Y]]]
such that τ
is equal to σ
.
Let
import notation.{I}
import implementation.generic.identityEndoFunctor
class EndoYonedaLemmaForIdentity[C[-_, +_]: FunctionalCategory, Z] extends EndoYonedaLemma[C, Z, I]
If G
is I
, then, with every endo natural transformation with morphism τ
of type C[YEFZ[Y], GF[Y]]
corresponds a global global value `u-->(u-->z)` = τ o v2gv(`z-->z`)
of type (GF O GF)[Z]
and with every
global global value `u-->(u-->z)`
of type (GF O GF)[Z]
corresponds an endo natural transformation with
morphism σ = μ o f2m(`z-->y` => gf(`z-->y`) o `u-->(u-->z)`)
of type C[YEFZ[Y], GF[Y]]]
such that
τ
is equal to σ
.
def endoCorollary1[Y]: EndoNaturalTransformation[C, YEFZ, G] => Law[C[YEFZ[Y], (GF O G)[Y]]] =
yef2g =>
import functionalCategory.{ν}
type YEFZ = [Y] =>> Yoneda[C][Z][Y]
def σ[Y]: C[YEFZ[Y], G[Y]] = yef2g.τ
endoYonedaLemma1[Y](
new:
override def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] =
ν o σ
)
preEndoYonedaLemma1
for PreFunctionalCategory
can be seen as a special case of endoYonedaLemma1
for
FunctionalCategory
.
import notation.{Proof}
class EndoYonedaProof[Z, C[-_, +_]: FunctionalCategory, G[+_]: [G[+_]] =>> EndoFunctor[C, G]]:
val functionalCategory = summon[FunctionalCategory[C]]
import functionalCategory.{GF}
import notation.{Yoneda}
type YEFZ = [Y] =>> Yoneda[C][Z][Y]
def g[Z, Y]: C[Z, Y] => C[G[Z], G[Y]] = summon[EndoFunctor[C, G]].f
def endoYonedaProof1[Y]: EndoNaturalTransformation[C, YEFZ, GF O G] => (C[Z, Y] => Proof[(GF O GF O G)[Y]]) =
yefz2gfog =>
import functionalCategory.{identity, f2m, v2gv, yefz, gf, ν, μ}
val `z-->z` = identity[Z]
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz2gfog.τ
def `u-->(u-->g[z])` : (GF O GF O G)[Z] = τ o v2gv(`z-->z`)
val yefz_2_gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] =
μ o f2m(`z-->y` => (gf o g)(`z-->y`) o `u-->(u-->g[z])`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz_2_gfog.τ
`z-->y` =>
Proof(
τ o v2gv(`z-->y`),
// rightIdentityLaw for C
τ o v2gv(`z-->y` o `z-->z`),
// pointfreeYoneda
τ o yefz(`z-->y`) o v2gv(`z-->z`),
// commutativityLaw for τ
(gf o g)(`z-->y`) o τ o v2gv(`z-->z`),
// definition `u-->(u-->g[z])`
(gf o g)(`z-->y`) o `u-->(u-->g[z])`,
// muProposition with `u-->(u-->y)` = (gf o g)((`z-->y`)) o `u-->(u-->g[z])`
μ o v2gv((gf o g)((`z-->y`)) o `u-->(u-->g[z])`),
// pointfreeApplication
μ o f2m[C[Z, Y], (GF O GF O G)[Y]](`z-->y` => (gf o g)(`z-->y`) o `u-->(u-->g[z])`) o v2gv(`z-->y`),
// definition σ
σ o v2gv(`z-->y`)
// done
)
def endoYonedaProof2[Y, X]: (GF O GF O G)[Z] => (C[Y, X] => (C[Z, Y] => Proof[(GF O GF O G)[X]])) =
`u-->(u-->g[z])` =>
`y-->x` =>
import functionalCategory.{f2m, v2gv, yefz, gf, μ}
val yefz2gfog: EndoNaturalTransformation[C, YEFZ, GF O G] =
new:
def τ[Y]: C[YEFZ[Y], (GF O G)[Y]] = μ o f2m(`z-->Y` => (gf o g)(`z-->Y`) o `u-->(u-->g[z])`)
def σ[Y]: C[YEFZ[Y], (GF O G)[Y]] = yefz2gfog.τ
`z-->y` =>
Proof(
(gf o g)(`y-->x`) o σ o v2gv(`z-->y`),
// definition σ
(gf o g)(`y-->x`) o (μ o f2m[C[Z, Y], (GF O GF O G)[Y]](`z-->y` =>
(gf o g)(`z-->y`) o `u-->(u-->g[z])`
)) o v2gv(`z-->y`),
// pointfreeApplication
(gf o g)(`y-->x`) o (μ o v2gv((gf o g)(`z-->y`) o `u-->(u-->g[z])`)),
// μ proposition for (GF, μ, ν)
(gf o g)(`y-->x`) o (gf o g)(`z-->y`) o `u-->(u-->g[z])`,
// functorCompositionProposition for gf o g
(gf o g)(`y-->x` o `z-->y`) o `u-->(u-->g[z])`,
// functionalCategoryMuProposition
μ o v2gv((gf o g)(`y-->x` o `z-->y`) o `u-->(u-->g[z])`),
// pointfreeApplication
μ o f2m[C[Z, X], (GF O GF O G)[X]](`z-->x` => (gf o g)(`z-->x`) o `u-->(u-->g[z])`) o v2gv(
`y-->x` o `z-->y`
),
// definition σ
σ o v2gv(`y-->x` o `z-->y`),
// pointfreeYoneda
σ o yefz(`y-->x`) o v2gv(`z-->y`)
)
Did we use all FunctionalCategory laws
?
Let's list them again
μ
commutativityLaw
for tripleGF
μ
andν
leftIdentityLaw
for tripleGF
μ
andν
rightIdentityLaw
for tripleGF
μ
associativityLaw
for tripleGF
f2m
compositionLaw
for functorFunctor[Function, C, I]
f2m
identityLaw
for functorFunctor[Function, C, I]
ν
commutativityLaw
for preTripleGF
ν
nuLaw
for preTripleGF
pointfreeApplicationProof
uses 5.pointfreeYonedaProof
uses 2.muProof
uses 8. and 2.`preEndoYonedaProof1
ofPreFunctionalCategory
uses 7.