diff --git a/build.sbt b/build.sbt index 027cbcd..cb3111b 100644 --- a/build.sbt +++ b/build.sbt @@ -9,5 +9,7 @@ lazy val root = (project in file(".")). )), name := "Category Theory", logBuffered in Test := false, - libraryDependencies ++= backendDeps + libraryDependencies ++= backendDeps, + resolvers += Resolver.sonatypeRepo("releases"), + addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.4") ) diff --git a/project/plugins.sbt b/project/plugins.sbt index d6115b1..b7b42e3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1 +1 @@ -addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.2") +// addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.2") diff --git a/src/main/scala/elbaulp/Category.scala b/src/main/scala/elbaulp/Category.scala index 1486712..4b406d5 100644 --- a/src/main/scala/elbaulp/Category.scala +++ b/src/main/scala/elbaulp/Category.scala @@ -1,29 +1,73 @@ package elbaulp +/* Typeclass to represent "any" category */ +trait Category[~>[_, _]] { + + /* Abstract typeclass methods */ + + def id[A]: A ~> A + + def compose[A, B, C](f: B ~> C, g: A ~> B): A ~> C + + /* Category laws */ + + trait Laws { + + def leftIdentity[A, B](f: A ~> B)(implicit Eq: Equal[A ~> B]): Boolean = + Eq.equal(compose(f, id), f) + + def rightIdentity[A, B](f: A ~> B)(implicit Eq: Equal[A ~> B]): Boolean = + Eq.equal(compose(id, f), f) + + def associativity[A, B, C, D]( + f: A ~> B, + g: B ~> C, + h: C ~> D)(implicit + Eq: Equal[A ~> D]): Boolean = + Eq.equal(compose(compose(h, g), f), compose(h, compose(g, f))) + } +} + +/* Category companion object, where typeclass instances rely */ object Category { - // Identity object of a category - // Bartosz Milewski Essence of Composition Challenge 1. - /** - * The Identity function. - * @params x The input - * @return the given input - * - * Identity properties: - * - f ∘ id = f = id ∘ f - */ - def Id[T](x: T) = x - - // Bartosz Milewski Essence of Composition Challenge 2. - // Implement the composition function in your favorite language. - // It takes two functions as arguments and returns a function that is their composition. - /** - * Composition function. - * @params f a function from A -> B - * @params g a function from B -> C - * @return The composition of the two, g ∘ f - * - * Composition properties: - * - Associativity: h∘(g∘f) = (h∘g)∘f = h∘g∘f - */ - def compose[A, B, C](f: A => B, g: B => C): A => C = f andThen g + + def apply[~>[_, _]](implicit C: Category[? ~> ?]): Category[? ~> ?] = C + + // Hask instance of category, where objects are types and arrows are functions + implicit val haskCategory: Category[? => ?] = new Category[? => ?] { + + // Identity object of a category + // Bartosz Milewski Essence of Composition Challenge 1. + /** + * The Identity function. + * @return the identity function + * + * Identity properties: + * - f ∘ id = f = id ∘ f + */ + def id[A] = identity + + // Bartosz Milewski Essence of Composition Challenge 2. + // Implement the composition function in your favorite language. + // It takes two functions as arguments and returns a function that is their composition. + /** + * Composition function. + * @params f a function from B -> C + * @params g a function from A -> B + * @return The composition of the two, f ∘ g + * + * Composition properties: + * - Associativity: h∘(g∘f) = (h∘g)∘f = h∘g∘f + */ + def compose[A, B, C](f: B => C, g: A => B): A => C = f compose g + } +} + +// Typeclass to define laws in a generic way +trait Equal[A] { + def equal(a1: A, a2: A): Boolean +} + +object Equal { + // ... (instances to enable scalacheck testing) } diff --git a/src/main/scala/elbaulp/Hello.scala b/src/main/scala/elbaulp/Hello.scala index b43faee..b68d196 100644 --- a/src/main/scala/elbaulp/Hello.scala +++ b/src/main/scala/elbaulp/Hello.scala @@ -2,5 +2,5 @@ package elbaulp object Hello extends App { println("Hello") - println(Category.Id(11111)) + println(Category.haskCategory.id(11111)) } diff --git a/src/test/scala/elbaulp/CategorySpec.scala b/src/test/scala/elbaulp/CategorySpec.scala index ec26577..e9cec2f 100644 --- a/src/test/scala/elbaulp/CategorySpec.scala +++ b/src/test/scala/elbaulp/CategorySpec.scala @@ -2,9 +2,9 @@ import elbaulp.Category import org.scalacheck.Prop._ object fixtures { - val f = (x: Int) => x.toDouble + val f = (z: Double) => z.toString val g = (y: Double) => y * y - val h = (z: Double) => z.toString + val h = (x: Int) => x.toDouble def square(a: Int) = a * a } @@ -15,13 +15,13 @@ class CategoryBDDSpec extends BddSpec { "A Category" - { "When calling its Identity" - { "Should be computed correctly" in { - assert(Category.Id(10) == 10) + assert(Category[? => ?].id(10) == 10) } } "When composing it" - { "Should be associative" in { - assert(Category.compose(Category.compose(f, g), h)(1) == - Category.compose(f, Category.compose(g, h))(1)) + assert(Category[? => ?].compose(Category[? => ?].compose(f, g), h)(1) == + Category[? => ?].compose(f, Category[? => ?].compose(g, h))(1)) } } } @@ -34,25 +34,25 @@ class CategoryPropSpec extends CheckSpec { property("a == Id(a)") { check(forAll { i:String => - Category.Id(i) === i + Category[? => ?].id(i) === i }) } property("Id∘f = f") { check(forAll { i: Int => - Category.Id(square(i)) === square(i) + Category[? => ?].id(square(i)) === square(i) }) } property("f∘Id = f") { check(forAll { i: Int => - f(Category.Id(i)) === f(i) + f(Category[? => ?].id(i)) === f(i) }) } property("Associativity: h∘(g∘f) = (h∘g)∘f = h∘g∘f"){ check(forAll { i: Int => - Category.compose(Category.compose(f, g), h)(i) === Category.compose(f, Category.compose(g, h))(i) + Category[? => ?].compose(Category[? => ?].compose(f, g), h)(i) === Category[? => ?].compose(f, Category[? => ?].compose(g, h))(i) }) } }