Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.2")
// addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.2")
94 changes: 69 additions & 25 deletions src/main/scala/elbaulp/Category.scala
Original file line number Diff line number Diff line change
@@ -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)
}
2 changes: 1 addition & 1 deletion src/main/scala/elbaulp/Hello.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package elbaulp

object Hello extends App {
println("Hello")
println(Category.Id(11111))
println(Category.haskCategory.id(11111))
}
18 changes: 9 additions & 9 deletions src/test/scala/elbaulp/CategorySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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))
}
}
}
Expand All @@ -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)
})
}
}