Skip to content

Instantly share code, notes, and snippets.

@klauso
Created February 25, 2020 18:14
Show Gist options
  • Save klauso/e44f73873aa3bca90130545b51e61525 to your computer and use it in GitHub Desktop.
Save klauso/e44f73873aa3bca90130545b51e61525 to your computer and use it in GitHub Desktop.
package test
import scala.language.higherKinds
import scala.language.implicitConversions
object CT {
trait Category[Obj[_],C[_,_]] { // Obj is the value representation of objects, C the type representation of morphisms
def id[A:Obj]: C[A,A]
def compose[X:Obj,Y:Obj,Z:Obj](f: C[Y,Z], g: C[X,Y]): C[X,Z]
}
abstract class Functor[ObjC[_],HomC[_,_],ObjD[_],HomD[_,_],F[_]](implicit val c: Category[ObjC,HomC],implicit val d: Category[ObjD,HomD]) {
def fmap[A:ObjC,B:ObjC](f: HomC[A,B]):HomD[F[A],F[B]]
implicit def apply[A:ObjC] : ObjD[F[A]]
}
abstract class Monad[Obj[_],Hom[_,_],M[_] ](implicit val C: Category[Obj,Hom]) extends Functor[Obj,Hom,Obj,Hom,M]{
def unit[A:Obj]:Hom[A,M[A]]
def bind[A:Obj,B:Obj](f: Hom[A,M[B]]) : Hom[M[A],M[B]]
def fmap[A:Obj, B:Obj](f: Hom[A,B]): Hom[M[A],M[B]] = bind(C.compose(unit, f))
}
abstract class Comonad[Obj[_],Hom[_,_],M[_] ](implicit val C: Category[Obj,Hom]) extends Functor[Obj,Hom,Obj,Hom,M] {
def counit[A:Obj]:Hom[M[A],A]
def cobind[A:Obj,B:Obj](f: Hom[M[A],B]) : Hom[M[A],M[B]]
def fmap[A:Obj, B:Obj](f: Hom[A,B]): Hom[M[A],M[B]] = {
cobind(C.compose(f,counit))
}
}
abstract class Adjunction[CObj[_],CHom[_,_],DObj[_],DHom[_,_],F[_],G[_]]
(val F: Functor[CObj,CHom,DObj,DHom,F], val G: Functor[DObj,DHom,CObj,CHom,G])(implicit val C: Category[CObj,CHom], D: Category[DObj,DHom]) { ad =>
implicit def Ff[A:CObj](x: A) : DObj[F[A]] = F.apply(implicitly)
implicit def Gg[A:DObj](x: A) : CObj[G[A]] = G.apply(implicitly)
//protected implicit def ev[A:CObj] : DObj[F[A]] = F(implicitly)
//protected implicit def ev2[A:CObj] : CObj[G[F[A]]] = G(F(implicitly))
//protected implicit def ev3[B:DObj] : CObj[G[B]] = G(implicitly[DObj[B]])
//protected implicit def ev4[B:DObj] : DObj[F[G[B]]] = F(G(implicitly))
def unit[A:CObj]: CHom[A,G[F[A]]]
def counit[A: DObj]: DHom[F[G[A]],A]
def leftAdjunct[A:CObj,B:DObj](f : DHom[F[A],B]) : CHom[A,G[B]] = {
C.compose(G.fmap(f),unit)
}
def rightAdjunct[A:CObj,B:DObj](f : CHom[A,G[B]]) : DHom[F[A],B] = {
D.compose(counit,F.fmap(f))
}
def monad : Monad[CObj,CHom,({type λ[α] = G[F[α]]})#λ] = new Monad[CObj,CHom,({type λ[α] = G[F[α]]})#λ]{
def bind[A:CObj, B:CObj](f: CHom[A,G[F[B]]]): CHom[G[F[A]],G[F[B]]] = G.fmap(rightAdjunct(f))
def unit[A:CObj]:CHom[A,G[F[A]]] = ad.unit
def apply[A: CObj] : CObj[G[F[A]]] = implicitly
}
def comonad : Comonad[DObj,DHom,({type λ[α] = F[G[α]]})#λ] = new Comonad[DObj,DHom,({type λ[α] = F[G[α]]})#λ] {
def counit[A:DObj] = ad.counit
def cobind[A:DObj,B:DObj](f: DHom[F[G[A]],B]) : DHom[F[G[A]],F[G[B]]] = F.fmap(leftAdjunct(f))
def apply[A:DObj] : DObj[F[G[A]]] = implicitly
}
}
// The category of Scala types and functions
implicit object ScalO {}
type ScalObj[A] = ScalO.type // no value representation of the objects (= Scala types)
type ScalHom[A,B] = A => B
implicit object Scal extends Category[ScalObj,ScalHom] {
def id[A:ScalObj] = (x:A) => x
def compose[X:ScalObj,Y:ScalObj,Z:ScalObj](f: Y=>Z, g: X=>Y ): X=>Z = f.compose(g)
}
// The category of monoids
trait Monoid[M] {
def plus(x: M, y: M) : M
def zero : M
}
type MonHom[M,N] = M => N
type MonObj[M] = Monoid[M]
implicit object Mon extends Category[MonObj,MonHom] {
def id[A:Monoid] = (x: A) => x
def compose[X:Monoid,Y:Monoid,Z:Monoid](f: Y=>Z, g: X=>Y): X=>Z = f.compose(g)
}
// some concrete monoids
def freeMonoid[M] = new Monoid[List[M]] {
def plus(x: List[M], y: List[M]) = x ++ y
def zero = List.empty
}
def intplusMonoid = new Monoid[Int] {
def plus(x: Int, y: Int) = x+y
def zero = 0
}
// The free/forgetful adjunction for monoids:
object FreeMonoidFunctor extends Functor[ScalObj,ScalHom,MonObj,MonHom,List] {
def fmap[A:ScalObj,B:ScalObj](f: A=>B) : List[A] => List[B] = m => m.map(f)
def apply[A:ScalObj] : MonObj[List[A]] = freeMonoid[A]
}
object MonoidForgetFunctor extends Functor[MonObj,MonHom,ScalObj,ScalHom,({type λ[α] = α})#λ] {
def fmap[A:MonObj,B:MonObj](f: A=>B) =f
def apply[A:MonObj] = ScalO
}
object FreeForgetfulMonoidAdjunction extends Adjunction[ScalObj,ScalHom,MonObj,MonHom,List,({type λ[α] = α})#λ](FreeMonoidFunctor,MonoidForgetFunctor) {
def counit[A:MonObj]: List[A] => A = as => as.foldRight(implicitly[Monoid[A]].zero)(implicitly[Monoid[A]].plus _)
def unit[A:ScalObj]: A => List[A] = a => List(a)
}
// just for completeness, the curry/uncurry adjunction
def pairFunctor[S] = new Functor[ScalObj,ScalHom,ScalObj,ScalHom,({type λ[α] = (S, α)})#λ] {
def fmap[A:ScalObj,B:ScalObj](f: A=>B) = (p) => (p._1,f(p._2))
def apply[A: ScalObj] = ScalO
}
def readerFunctor[S] = new Functor[ScalObj,ScalHom,ScalObj,ScalHom,({type λ[α] = S => α})#λ] {
def fmap[A:ScalObj,B:ScalObj](f: A=>B) = g => s => f(g(s))
def apply[A: ScalObj] = ScalO
}
def CurryUncurryAdjunction[S] = new Adjunction[ScalObj,ScalHom,ScalObj,ScalHom,({type λ[α] = (S,α)})#λ,({type λ[α] = S => α})#λ](pairFunctor,readerFunctor) {
def counit[A:ScalObj] = p => p._2(p._1)
def unit[A:ScalObj]: A => S => (S, A) = a => s => (s,a)
}
// The category of partial orders
trait PartialOrder[T] {
def leq(x: T, y: T) : Boolean
}
def PosetCategory[T](po: PartialOrder[T]) = new Category[({type λ[α] = T})#λ, ({type λ[X,Y] = (T,T)})#λ] {
def compose[X, Y, Z](f: (T, T), g: (T, T))(implicit x: T, y: T, z: T) = (x,z)
def id[A](implicit o: T): (T,T) = (o,o)
}
// the category of sets partially ordered by subset inclusion
implicit def subsetPO[T] = PosetCategory(new PartialOrder[Set[T]] { def leq(x: Set[T], y:Set[T]) = x.subsetOf(y) })
// the category of integer intervals ordered by interval inclusion
implicit def intervalPO = PosetCategory[(Int,Int)](new PartialOrder[(Int,Int)] { def leq(x: (Int,Int), y: (Int,Int)) = x._1 >= y._1 && x._2 <= y._2 })
// the Galois connection between sets of integers and integer intervals
def abstractionFunctor = new Functor[({type λ[α] = Set[Int]})#λ,
({type λ[X,Y] = (Set[Int],Set[Int])})#λ,
({type λ[α] = (Int,Int)})#λ,
({type λ[X,Y] = ((Int,Int),(Int,Int))})#λ,
({type λ[X] = (Int,Int)})#λ]()(subsetPO[Int],intervalPO) {
override def apply[A](implicit x: Set[Int]): (Int,Int) = (x.min, x.max)
override def fmap[A, B](f: (Set[Int],Set[Int]))(implicit x: Set[Int], y: Set[Int]): ((Int, Int),(Int,Int)) = (this(f._1),this(f._2))
}
def concretizationFunctor = new Functor[({type λ[α] = (Int,Int)})#λ,
({type λ[X,Y] = ((Int,Int),(Int,Int))})#λ,
({type λ[α] = Set[Int]})#λ,
({type λ[X,Y] = (Set[Int],Set[Int])})#λ,
({type λ[X] = Set[Int]})#λ]()(intervalPO,subsetPO[Int]) {
def apply[A](implicit x: (Int,Int)): Set[Int] = (x._1 to x._2).toSet
def fmap[A, B](f: ((Int,Int),(Int,Int)))(implicit x: (Int,Int), y: (Int,Int)): (Set[Int],Set[Int]) = (this(f._1),this(f._2))
}
object GaloisConnection extends Adjunction[({type λ[α] = Set[Int]})#λ,
({type λ[X,Y] = (Set[Int],Set[Int])})#λ,
({type λ[α] = (Int,Int)})#λ,
({type λ[X,Y] = ((Int,Int),(Int,Int))})#λ,
({type λ[X] = (Int,Int)})#λ,
({type λ[X] = Set[Int]})#λ](abstractionFunctor,concretizationFunctor) {
def counit[A](implicit x: (Int, Int)): ((Int, Int), (Int, Int)) = (x,x)
def unit[A](implicit x: Set[Int]): (Set[Int], Set[Int]) = (x,x)
}
trait CartesianClosedCategory[Obj[_],C[_,_],P[_,_],E[_,_]] extends Category[Obj,C] {
def mkProd[X:Obj,Y:Obj,Z: Obj](a: C[X,Z], b: C[X,Y]) : C[X,P[Z,Y]]
def fst[A:Obj,B:Obj]: C[P[A,B],A]
def snd[A:Obj,B:Obj]: C[P[A,B],B]
def exponential[X:Obj,Y:Obj, Z:Obj](g : C[P[X,Y],Z]) : C[X,E[Z,Y]]
def eval[Y:Obj,Z:Obj] : C[P[E[Z,Y],Y],Z]
}
implicit object ScalCC extends CartesianClosedCategory[ScalObj,ScalHom,({type λ[X,Y] = (X,Y)})#λ,({type λ[X,Y] = Y=>X})#λ] {
def id[A:ScalObj] = (x:A) => x
def compose[X:ScalObj,Y:ScalObj,Z:ScalObj](f: Y=>Z, g: X=>Y ): X=>Z = f.compose(g)
def product[A:ScalObj, B:ScalObj]: ScalObj[(A, B)] = ScalO
def fst[A:ScalObj, B:ScalObj] : ((A,B)) => A = p => p._1
def snd[A:ScalObj, B:ScalObj] : ((A,B)) => B = p => p._2
def mkProd[X:ScalObj,Y:ScalObj,Z:ScalObj](a: X=>Z, b: X=>Y) : X =>(Z,Y) = (x) => (a(x),b(x))
def exponential[X:ScalObj, Y:ScalObj, Z:ScalObj](g: ((X, Y))=>Z): X => Y => Z = x => y => g((x,y))
def eval[Y:ScalObj,Z:ScalObj] : ((Y=>Z,Y)) => Z = p => p._1(p._2)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment