dnvriend / type-classes-example

A small study project on type classes, level of entry is 'beginner'

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

type-classes-example

A small study project on type classes, level of entry is 'beginner'.

Using the example

Just clone the project:

git clone git@github.com:dnvriend/type-classes-example.git

Enter the directory and launch sbt.

Launching the test

To launch the tests:

sbt test

Launch the REPL

To launch the REPL with scalaz, simulacrum, play-json and akka-http-spray-json on the classpath so you can use the abstractions of said libraries:

sbt console

Type Classes

The following is taken from the fantastic book: The Type Astronaut's Guide to Shapeless - Underscore.

Type classes are a programming pattern borrowed from the programming language Haskell. The word 'class' has nothing to do with classes in object oriented programming.

We encode type classes in Scala using traits and implicits. A type class is a parameterised trait representing some general functionality that we would like to apply to a wide range of types:

trait CsvEncoder[A] {
  def encode(value: A): List[String]
}

We implement our type class with instances for each type we care about. If we want the instances to automatically be in scope we can place them in the type class’ companion object. Otherwise we can place them in a separate library object for the user to import manually:

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait CsvEncoder[A] {
  def encode(value: A): List[String]
}

case class Person(name: String, age: Int, married: Boolean)

object Person {
  implicit val encoder = new CsvEncoder[Person] {
    override def encode(person: Person): List[String] =
      List(
        person.name,
        person.age.toString,
        if(person.married) "yes" else "no"
      )
  }
}

def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
  values.map(value => encoder.encode(value).mkString(",")).mkString("\n")

val people = List(Person("Foo", 42, false), Person("Bar", 25, true))

writeCsv(people)

// Exiting paste mode, now interpreting.

defined trait CsvEncoder
defined class Person
defined object Person
writeCsv: [A](values: List[A])(implicit encoder: CsvEncoder[A])String
people: List[Person] = List(Person(Foo,42,false), Person(Bar,25,true))
res5: String =
Foo,42,no
Bar,25,yes

When we call writeCsv, the compiler calculates the value of the type parameter and searches for an implicit CsvEncoder of the corresponding type which in this case is CsvEncoder[Person]. We can use writeCsv with any data type we like, provided we have a corresponding implicit CsvEncoder in scope:

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class Address(street: String, houseNumber: Int, zipcode: String)
object Address {
  implicit val encoder = new CsvEncoder[Address] {
    override def encode(address: Address): List[String] =
      List(
        address.street,
        address.houseNumber.toString,
        address.zipcode
      )
  }
}

val addresses = List(Address("FooStreet", 1, "1000AA"), Address("BarStreet", 1, "1500AB"))

writeCsv(addresses)

// Exiting paste mode, now interpreting.

defined class Address
defined object Address
addresses: List[Address] = List(Address(FooStreet,1,1000AA), Address(BarStreet,1,1500AB))
res6: String =
FooStreet,1,1000AA
BarStreet,1,1500AB

Idiomatic Type Class Definitions

The following is taken from the fantastic book: The Type Astronaut's Guide to Shapeless - Underscore.

The commonly accepted idiomatic style for type class definitions starts with a parameterized trait that defines some general functionality that we would like to apply to a wide range of types.

The next thing developers that create type classes often do is create a companion object that contains some standard methods that provide the following functionality:

  • an 'apply' method that is known as a 'summoner' or 'materializer' that when called returns a type class instance of a given target type,
  • an 'instance' method (that is sometimes named 'pure') that provides a terse syntax for creating new type class instances thus reducing writing boilerplate code
object CsvEncoder {
  // The 'summoner' or 'materializer' method, when called with a propert type returns a type class instance
  def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] = enc
  // The 'constructor' method that helps us writing less code when defining type class instances
  def instance[A](f: A => List[String]): CsvEncoder[A] = (value: A) => f(value)
}

When using this convention our example will look like:

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait CsvEncoder[A] {
  def encode(value: A): List[String]
}

object CsvEncoder {
  // "Summoner" method
  def apply[A](implicit enc: CsvEncoder[A]): CsvEncoder[A] = enc
  // "Constructor" method
  def instance[A](f: A => List[String]): CsvEncoder[A] = (value: A) => f(value)
  // Globally visible type class instances
}

case class Person(name: String, age: Int, married: Boolean)

object Person {
  implicit val encoder = CsvEncoder.instance[Person] { person =>
    List(
      person.name,
      person.age.toString,
      if(person.married) "yes" else "no"
    )
  }
}

def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
  values.map(value => encoder.encode(value).mkString(",")).mkString("\n")

val people = List(Person("Foo", 42, false), Person("Bar", 25, true))


case class Address(street: String, houseNumber: Int, zipcode: String)
object Address {
  implicit val encoder = CsvEncoder.instance[Address] { address =>
    List(
      address.street,
      address.houseNumber.toString,
      address.zipcode
    )
  }
}

val addresses = List(Address("FooStreet", 1, "1000AA"), Address("BarStreet", 1, "1500AB"))

val peopleCsv = writeCsv(people)
val addressCsv = writeCsv(addresses)

// Exiting paste mode, now interpreting.

defined trait CsvEncoder
defined object CsvEncoder
defined class Person
defined object Person
writeCsv: [A](values: List[A])(implicit encoder: CsvEncoder[A])String
people: List[Person] = List(Person(Foo,42,false), Person(Bar,25,true))
defined class Address
defined object Address
addresses: List[Address] = List(Address(FooStreet,1,1000AA), Address(BarStreet,1,1500AB))
peopleCsv: String =
Foo,42,no
Bar,25,yes
addressCsv: String =
FooStreet,1,1000AA
BarStreet,1,1500AB

Type classes and libraries

Type classes is a pattern that is also being used by library designers so you will see the pattern used in Scala libraries as well. For example, the play-json library uses type classes the same way as the example above to convert a type but then of course to JSON.

To use play-json, we must first add a dependency to that library so we can use it from our source code:

libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.0-M1"

Play-json uses the play.api.libs.json.Format[A] type class to define a generic way to convert types to a play.api.libs.json.JsValue. JsValue is the most abstract definition of what play-json defines being a JsonValue. Because JsValue a sealed trait, JsValue defines an enumeration of what other types a Json-encoded type consists of.

So play-json can convert any type A to a JsValue (A => JsValue), but only when there is a type class defined that encapsulates how that should be done.

Play-json already knows how to convert a JsValue to String (JsValue => String) and that is easy:

def writeJson[A](value: A)(implicit format: Format[A]): String =
  Json.toJson(value).toString

So the only thing we need is a type class called 'Format' of the types we care to convert to Json and we can serialize those types to a Json encoded String.

We can use the same strategy as we did with converting to a CSV so we can add a type class instance to the companion object of Person and of Address so we can convert those types into a JsValue:

import play.api.libs.json._

case class Person(name: String, age: Int, married: Boolean)

object Person {
  implicit val format = Json.format[Person]
}

We can now write a Person to Json:

scala> writeJson(Person("foo", 42, false))
res0: String = {"name":"foo","age":42,"married":false}

The syntax to create a type class instance for 'Format' is very terse, that is because we are using a macro that has been provided by play-json that creates the necessary source code for use to be able to convert a Person into Json.

We can also convert a Json encoded String back to a Person case class with the same type class:

scala> Json.parse("""{"name":"foo","age":42,"married":false}""").as[Person]
res2: Person = Person(foo,42,false)

The example below shows how we can convert a list of types to Json

scala> :paste
// Entering paste mode (ctrl-D to finish)

import play.api.libs.json._

case class Person(name: String, age: Int, married: Boolean)

object Person {
  implicit val format = Json.format[Person]
}

val people = List(Person("Foo", 42, false), Person("Bar", 25, true))

case class Address(street: String, houseNumber: Int, zipcode: String)
object Address {
  implicit val format = Json.format[Address]
}

val addresses = List(Address("FooStreet", 1, "1000AA"), Address("BarStreet", 1, "1500AB"))

def writeJson[A](value: A)(implicit format: Format[A]): String =
  Json.toJson(value).toString

val peopleJson = writeJson(people)
val addressJSon = writeJson(addresses)

// Exiting paste mode, now interpreting.

import play.api.libs.json._
defined class Person
defined object Person
people: List[Person] = List(Person(Foo,42,false), Person(Bar,25,true))
defined class Address
defined object Address
addresses: List[Address] = List(Address(FooStreet,1,1000AA), Address(BarStreet,1,1500AB))
writeJson: [A](value: A)(implicit format: play.api.libs.json.Format[A])String
peopleJson: String = [{"name":"Foo","age":42,"married":false},{"name":"Bar","age":25,"married":true}]
addressJSon: String = [{"street":"FooStreet","houseNumber":1,"zipcode":"1000AA"},{"street":"BarStreet","houseNumber":1,"zipcode":"1500AB"}]

To parse a list of people:

scala> Json.parse("""[{"name":"Foo","age":42,"married":false},{"name":"Bar","age":25,"married":true}]""").as[List[Person]]
res4: List[Person] = List(Person(Foo,42,false), Person(Bar,25,true))

To parse a list of addresses:

scala> Json.parse("""[{"street":"FooStreet","houseNumber":1,"zipcode":"1000AA"},{"street":"BarStreet","houseNumber":1,"zipcode":"1500AB"}]""").as[List[Address]]
res5: List[Address] = List(Address(FooStreet,1,1000AA), Address(BarStreet,1,1500AB))

Ad-hoc polymorphism

Ad-hoc polymorphism is an alternative to sub-type polymorphism and is a way to convert a type A into another type B without the types having the knowledge of convertyping one type into another and vice versa. Instead a third 'adapter' class will be used that converts type A into type B. This pattern can be easily implemented using the adapter-pattern in which we can isolate all the transformation logic in the adapter.

When implemented in Scala using a combination of parameterized trait, implicit parameter and currying, the 'adapter' can automatically be injected when an appropriate type is needed. When thinking more generic terms, such adapters abstract over types and can be thought a grouping of general functionality that can be applied to a range of types. This pattern is called the 'type class' pattern.

Simulacrum

The type class pattern is used to define some general functionality that we would like to apply to a wide range of types. The Scala programming language has no first class support for this pattern, which is a shame.

As we have seen, to define a type class we must first know about the type class pattern and then know about the idiomatic way (the commonly agreed upon way) how to encode them using a combination of trait, companion object and some methods that give us a way to work with them effectively. Wouldn't it be nice if Scala had first class support for type classes?

Yes it would, but as of Scala 2.12.1 there is still no first class language support for type classes. There is however a project called Simulacrum that adds first class syntax support for type classes to the Scala language.

When you add simulacrum to your project, you get a very concise way to define type classes and type class instances that leads to the same implementation (idiomatic way).

First we must alter our build.sbt file and add the following lines:

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full)

libraryDependencies += "com.github.mpilquist" %% "simulacrum" % "0.10.0"

The biggest change for us as a developer when using simulacrum is that we just have to define a parameterized trait and define the generic functionality and we don't have to know the (current) idiomatic way how to define the companion object of the trait. The companion object of the trait will be generated for us, which means that we now have first class support for type classes in Scala which is great!

We can now define a case class like so:

import simulacrum._

@typeclass trait CsvEncoder[A] {
  def encode(value: A): List[String]
}

The compiler plugin will generate a companion object for us containing all the necessary helper methods that makes working with type classes easy. Of course, we must still provide type class instances for the types we care about that, if possible, we can put in the companion object of the type we wish to convert.

For example:

import simulacrum._

@typeclass trait CsvEncoder[A] {
  def encode(value: A): List[String]
}

case class Person(name: String, age: Int, married: Boolean)

object Person {
  implicit val encoder = new CsvEncoder[Person] {
    override def encode(person: Person): List[String] = List(
      person.name,
      person.age.toString,
      if(person.married) "yes" else "no"
    )
  }
}

CsvEncoder[Person].encode(Person("foo", 42, true))

def writeCsv[A](values: List[A])(implicit encoder: CsvEncoder[A]): String =
 values.map(value => encoder.encode(value).mkString(",")).mkString("\n")

val people: List[Person] = List(Person("foo", 42, true), Person("bar", 25, false))

val peopleCsv: String = writeCsv(people)

Haskell Type Classes

There is another functional programming language besides Scala that uses type classes which is the Haskell programming language. Type classes are a great pattern because they provide general functionality across types. As we have seen we can define any functionality that we wish to provide for a group of types by just isolating that functionality in a trait and give an implementation for that functionality. So if we would like to serialize a type to CSV, we can isolate that functionality in a type class, or if we would like to serialize to JSON, we can isolate that functionality in a type class and so on.

But type classes are not limited to only serializing types. We can isolate any functionality in type classes. For example, we could choose to provide a way to convert a type into a textual representation by isolating that functionality into a type class or if we would like to add two types like say two numbers, we can isolate that functionality in a type class.

The standard Haskell libraries feature a number of type classes with algebraic or category-theoretic underpinnings but the library also provides some technical type classes like the type class Show, lets look at that type class.

Haskell Show

Show is a type class for conversion to textual representation.

import simulacrum._

@typeclass trait Show[A] {
  def show(value: A): String
}

We can use the Show type class to provide a way to convert a type into a textual representation. The only thing we must do is provide an instance of the type class for example:

import simulacrum._

@typeclass trait Show[A] {
  def show(value: A): String
}

case class Person(name: String, age: Int)
object Person {
  implicit val show = new Show[Person] {
    override def show(person: Person): String = {
      import person._
      s"""Person(name=$name, age=$age)"""
    }
  }
}

// we can now 'summon' a type class instance of Show[Person] by just typing
// Show[Person], that is a feature that has been created by simulacrum.
Show[Person].show(Person("foo", 42))

Scalaz Type Classes

Scalaz is an extension to the core Scala library for functional programming. Scalaz contains type classes that have been heavily inspired by the [Haskell Typeclassopedia] and those type classes are mostly based on algebraic or category-theoretic underpinnings.

Apart from the algebraic or category-theoretic type classes Scalaz has other type classes for example Equal that provides a typesafe alternative to test equality.

Haskell Typeclassopedia

Combining, combining, combining...

Functional programming is all about solving a problem by breaking down / decomposing a problem is very small problems and solve those small problems using functions. To be able to solve the big problem we must combine everything using combinators. Ehenever possible we must use functions to solve problems because you can easily combine/compose functions.

For example, to solve a problem like validating a list of input values we can do the following breakdown:

  • aggregate values in a List,
  • transform those values into an effect eg Validation
  • test whether there are errors
    • if there are errors, give me the errors
    • if there are no errors, give me the sum of the values

We could do it as follows:

import scalaz._
import Scalaz._

scala> def validateInput(input: String): ValidationNel[String, Int] = input.parseInt.leftMap(_.toString).toValidationNel
validateInput: (input: String)scalaz.ValidationNel[String,Int]

scala> List("1").map(validateInput).sequenceU.map(_.sum)
res0: scalaz.Validation[scalaz.NonEmptyList[String],Int] = Success(1)

scala> List("1", "2").map(validateInput).sequenceU.map(_.sum)
res1: scalaz.Validation[scalaz.NonEmptyList[String],Int] = Success(3)

scala> List("a", "b").map(validateInput).sequenceU.map(_.sum)
res2: scalaz.Validation[scalaz.NonEmptyList[String],Int] = Failure(NonEmpty[java.lang.NumberFormatException: For input string: "a",java.lang.NumberFormatException: For input string: "b"])

We have validated two strings and when they are numbers we get the Success sum of those numbers, else the Failure with all the failures.

We can solve this problem a bit shorter by using a Monoid and a utility method of Scalaz.

There exists a Monoid[ValidationNel[String, Int]] instance:

scala> Monoid[ValidationNel[String, Int]]
res0: scalaz.Monoid[scalaz.ValidationNel[String,Int]] = scalaz.ValidationInstances0$$anon$5@324f3f84

There is a convenience method on List that can use this monoid to combine the contents of the validation using that monoid instance:

import scalaz._
import Scalaz._

scala> def validateInput(input: String): ValidationNel[String, Int] = input.parseInt.leftMap(_.toString).toValidationNel
validateInput: (input: String)scalaz.ValidationNel[String,Int]

scala> List("1", "2").map(validateInput).suml
res0: scalaz.ValidationNel[String,Int] = Success(3)

scala> List("a", "2").map(validateInput).suml
res1: scalaz.ValidationNel[String,Int] = Failure(NonEmpty[java.lang.NumberFormatException: For input string: "a"])

The suml does a left fold and uses a Monoid instance for the element type of the list. We start out with a List[String], then List[ValidationNel[String, Int]]. The suml method needs a Monoid[ValidationNel[String, Int]] and when one is found then it will be used to sum up all the Ints of the List[ValidationNel[String, Int]].

When no Monoid[ValidationNel[String, Int]] is found, then the whole program won't even compile, because of Scala implicits.

Making things simple

Scalaz does not only provide a bunch of type classes that are heavily inspired by the type classes as defined by the Haskell programming language, it also provides a lot of convenience methods that make working with type classes and combining these type classes with data structures very useful. The learning curve is knowing the which convenience methods there are, which type classes there are, and when it is logical to combine a type class with a convencience method and a data structure for solving a problem.

Using type classes from standard libraries from for example Scalaz promotes standardization, developers start to recognize structures and over time makes the code very simple to read and for the Java runtime very easy to optimize.

Apart from the standardization and recognition, breaking down a problem is reusable parts like Semigroups, Monoids, Functors for example promotes reusable components because the type classes provide a very general abstraction because for most type classes you need a function f: A => B to operate on them, this means that the type class most of the time is unbiased about what you are doing (the function) but is biased about the context of the computation (Validation, JsonFormat, Option) and so on.

Algebra

Types are at the center of programming. A type is defined by the operations it provides and the laws of these operations. The laws of the operations is called the algebra of the type. When we think about the types we know like Int, Double or String we know that these types support arithmic operations like +, -, *, / and %. Moreover, most types share exactly the same behavior and that is a property that we can take advantage of in generic programming. We can now focus on the rules or rather the algebra of the computation.

Function

A function is a structure that relates input to output. In the scala programming language, a function can be defined as something that can be evaluated on demand, so each and every time we want to compute a value, we can apply a function.

In scala that is very easy to define:

scala> def addOne(x: Int): Int = x + 1
addOne: (x: Int)Int

scala> addOne(1)
res0: Int = 2

We can use some syntactic sugar to transform a method to a function:

scala> val f = addOne _
f: Int => Int = $$Lambda$1056/1003737182@6aba5d30

scala> f(1)
res1: Int = 2

Scala has very concise syntax for defining functions for example:

scala> val g = (_: Int) + 1
g: Int => Int = $$Lambda$1059/1021656938@2d82408

scala> g(1)
res2: Int = 2

But at the other end of the spectrum, we can use the very verbose and technical way of defining a function:

scala> val h = new Function1[Int, Int] {
     | override def apply(x: Int): Int = x + 1
     | }
h: Int => Int = <function1>

scala> h(1)
res3: Int = 2

Technical side note:_ As you can see, I am using the Scala 2.12 REPL. You can see that for all but the last example, Scala has optimized the function to be a Java8-lambda, which is a very resource optimized way for defining and executing Functions. When explicitly creating a function using the Function(N) trait, we are explicitly creating functions and those cannot be optimized to use lambdas.

Combining functions

Functions can be combined to create new functions using compose and andThen methods.

scala> val f = (_: Int) + 1
f: Int => Int = $$Lambda$1020/536671860@442f92e6

scala> val g = (_: Int) + 10
g: Int => Int = $$Lambda$1029/1134894336@2e23c180

scala> val h = f andThen g
h: Int => Int = scala.Function1$$Lambda$1036/1906029492@52cb4f50

scala> val i = f compose g
i: Int => Int = scala.Function1$$Lambda$1037/676338251@5b051a5c

scala> h(1)
res0: Int = 12

scala> i(1)
res1: Int = 12

Promoting methods to functions

In the Scala programming language it is possible to promote a method to a function by means of delegation which means a function is being created that will call our method. Scala supports this using a process called 'method values'. Methods can be promoted to a function because a method basically does the same as a function in that it relates input to output and does this on demand. Of course, a plain method isn't a function, but Scala can promote a plain method to a function.

But we need to define something extra here. Methods are often used in combination with objects to provide something called the 'universal access principle' and to provide encapsulation so it has everything to do with keeping state. So that is not the 'method-type' I am referring to; not a class method.

Scala can provide methods in a static context, and those methods operate in a stateless context. When defining methods on a 'module', which is a way of strucuring/organizing code, you can group related methods (and values/constants), under a given name. Those methods can be 'converted' to a function for example:

scala> object Foo {
     | val Pi: Double = 3.1415926
     | def addOne(x: Int): Int = x + 1
     | }
defined object Foo

scala> Foo.addOne _
res0: Int => Int = $$Lambda$1331/111131743@5a8dfd2e

We have created a module (an organizational unit) called 'Foo' using the keyword 'object' that makes it possible to have a stable reference to the module 'Foo' that contains values and methods. Methods defined in such a module can be promoted to functions using a process called 'exa-expansion' and is described in the Scala language specification - Method Values.

So Scala provides several ways to create functions from 'eta-expanding' a method to a function literal.

Domain

A domain is all the values that can go into a function.

For example, when we have the function like the example above, the domain are all the values from Int.MinValue and Int.MaxValue inclusive. This range of values can best be expressed as sets, which is a unique collection of values.

Sets can be written as:

{ ... , -3, -2, -1, 0, 1, 2, 3, ... }

Or in Scala:

scala> Set(-3, -2, -1, 0, 1, 2, 3)
res0: scala.collection.immutable.Set[Int] = Set(0, -3, 1, 2, 3, -1, -2)

Even better is to use the scala.collection.immutable.Range to define a domain.

scala> Range.inclusive(-3, 3).toList
res1: List[Int] = List(-3, -2, -1, 0, 1, 2, 3)

We can define a domain using scala.collection.immutable.Range and apply the function to the domain to get something that is called a Range in math terms, or in plain english, 'all the values that come out of a function when the function is applied on a certain domain'. We will use the .map method to apply the function to the domain:

scala> Range.inclusive(-3, 3).map(addOne)
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(-2, -1, 0, 1, 2, 3, 4)

To conclude the discussion, the co-domain is the range Range.inclusive(Int.MinValue, Int.MaxValue) so all the possible values that may come out of the function or in technical terms, the result type of the function which is Int.

Range

A Range is all the values that come out of a function when the function is applied on a certain domain.

Codomain

A codomain is all the values what may possibly come out of a function. In Scala, the range is defined by the return type of a function/method

Drawing domain, range and codomain

Domain, range and codomain are each defined as a Set eg. { ..., -3, -2, -1, 0, 1, 2, 3, ... } and these sets can be drawn using an oval in where you draw the values that do into that set. The circle contains the name of the set.

Between the sets there is an arrow representing a domain object that has been applied to the function and must point to the resuling range. The range is the output of the function and is another oval containing only the values that is the result of applying the function to the domain and is often called Y.

For an explanation read: Domain, Range and Codomain

Semigroup

A Semigroup is a pure algebraic structure that is defined by the Semigroup laws. Scalaz provides a type called scalaz.Semigroup, which provides an associative binary operation. Scalaz not only provides the type class but also instances of Semigroup for most standard scala types and Scalaz types like Validation that conform to the Semigroup laws.

A semigroup is a set of 'A' together with a binary operation def append(left: A, right: A): A with symbol |+| which combines elements from A. The |+| operator is required to be associative.

A semigroup in type A must satisfy two laws:

  • closure: '∀ a, b in F, append(a, b)' is also in 'F'. This is enforced by the type system.
  • associativity_: '∀ a, b, c` in F, the equation 'append(append(a, b), c) = append(a, append(b , c))' holds.

For example, the natural numbers under addition form a semigroup: the sum of any two natural numbers is a natural number, and (a+b)+c = a+(b+c) for any natural numbers a, b, and c,.

scala> val s = Semigroup[Int]
s: scalaz.Semigroup[Int] = scalaz.std.AnyValInstances$$anon$5@537d2634

scala> s.append(1, s.append(2, 3)) == s.append(s.append(1, 2), 3)
res0: Boolean = true

The integers under multiplication also form a semigroup, as do the integers, Boolean values under conjunction and disjunction, lists under concatenation, functions from a set to itself under composition.

Semigroups show up all over the place, once you know to look for them.

import scalaz._
import Scalaz._

scala> Semigroup[Int].append(1, 1)
res0: Int = 2

scala> 1 |+| 1
res1: Int = 2

scala> Semigroup[String].append("a", "b")
res2: String = ab

scala> "a" |+| "b"
res3: String = ab

scala> 1 |+| "1"
<console>:18: error: type mismatch;
 found   : String("1")
 required: Int
       1 |+| "1"

Semigroup instances

There are instances or Semigroup for:

scala> Semigroup[Short]
res0: scalaz.Semigroup[Short] = scalaz.std.AnyValInstances$$anon$4@5f1ee303

scala> Semigroup[Byte]
res1: scalaz.Semigroup[Byte] = scalaz.std.AnyValInstances$$anon$2@5965e7d0

scala> Semigroup[Int]
res2: scalaz.Semigroup[Int] = scalaz.std.AnyValInstances$$anon$5@77f3b689

scala> Semigroup[Long]
res3: scalaz.Semigroup[Long] = scalaz.std.AnyValInstances$$anon$6@79decd60

scala> Semigroup[BigDecimal]
res4: scalaz.Semigroup[BigDecimal] = scalaz.std.math.BigDecimalInstances$$anon$1@141f0b78

scala> Semigroup[String]
res5: scalaz.Semigroup[String] = scalaz.std.StringInstances$stringInstance$@41b29a0f

scala> Semigroup[List[Int]]
res6: scalaz.Semigroup[List[Int]] = scalaz.std.ListInstances$$anon$4@3773a554

scala> Semigroup[Set[Int]]
res7: scalaz.Semigroup[scala.collection.immutable.Set[Int]] = scalaz.std.SetInstances$$anon$3@60a88f23

scala> Semigroup[NonEmptyList[Int]]
res8: scalaz.Semigroup[scalaz.NonEmptyList[Int]] = scalaz.NonEmptyListInstances$$anon$2@38d65d41

scala> Semigroup[Vector[Int]]
res9: scalaz.Semigroup[scala.collection.immutable.Vector[Int]] = scalaz.std.VectorInstances$$anon$4@54e3c254

scala> Semigroup[Map[String, String]]
res10: scalaz.Semigroup[scala.collection.immutable.Map[String,String]] = scalaz.std.MapSubInstances$$anon$5@3f4bf1ca

scala> Semigroup[Map[String, List[Int]]]
res11: scalaz.Semigroup[scala.collection.immutable.Map[String,List[Int]]] = scalaz.std.MapSubInstances$$anon$5@29c645f6

scala> Semigroup[Disjunction[String, Int]]
res12: scalaz.Semigroup[scalaz.Disjunction[String,Int]] = scalaz.DisjunctionInstances$$anon$4@1747f915

scala> Semigroup[Validation[String, Int]]
res13: scalaz.Semigroup[scalaz.Validation[String,Int]] = scalaz.ValidationInstances0$$anon$5@65d1c6cc

scala> Semigroup[ValidationNel[String, Int]]
res14: scalaz.Semigroup[scalaz.ValidationNel[String,Int]] = scalaz.ValidationInstances0$$anon$5@49f5361d

scala> Semigroup[Option[Int]]
res15: scalaz.Semigroup[Option[Int]] = scalaz.std.OptionInstances$$anon$8@66c75201

scala> Semigroup[Future[Int]]
res16: scalaz.Semigroup[scala.concurrent.Future[Int]] = scalaz.Monoid$$anon$2@25efa795

scala> Semigroup[Int => Int]
res17: scalaz.Semigroup[Int => Int] = scalaz.std.FunctionInstances0$$anon$14@50ca7072

We could for example append two functions and apply the semigroup:

scala> val f = (_: Int) + 1
f: Int => Int = $$Lambda$1252/1700135698@120dc79c

scala> val g = (_: Int) + 10
g: Int => Int = $$Lambda$1253/1532807735@1a8e7046

scala> val s = f |+| g
s: Int => Int = scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@3dee6728

scala> s(1)
res0: Int = 13

scala> val t = s |+| s
t: Int => Int = scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@7724ed3c

scala> t(1)
res1: Int = 26

scala> val xs = List.fill(5)(s)
xs: List[Int => Int] = List(scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@3dee6728, scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@3dee6728, scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@3dee6728, scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@3dee6728, scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@3dee6728)

// is there a Monoid[Int => Int]?
scala> Monoid[List[Int => Int]]
res2: scalaz.Monoid[List[Int => Int]] = scalaz.std.ListInstances$$anon$4@2efc84ef

// so there is a Monoid for the element. We can now fold the List structure:
scala> val j = Foldable[List].fold(xs)
j: Int => Int = scalaz.std.Function1Semigroup$$Lambda$1268/1970485818@1d0b9fde

// we can now apply the resulting folded 'j' structure
scala> j(1)
res3: Int = 65

Associative Functions

Semigroups are associative functions:

f(a, f(b, c)) == f(f(a, b), c)

Monoid

A Monoid is a pure algebraic structure that is defined by the monoid laws. Scalaz provides a type called scalaz.Monoid, which provides an associative binary operation with an identity element ('zero'). Scalaz not only provides the type class but also instances of Monoid for most standard scala types and Scalaz types like Validation that conform to the Monoid laws.

Many semigroups have a special element 'zero' for which the binary operation def append(left: A, right: A): A with symbol |+| is the identity. Such a semigroup-with-identity-element is called a monoid.

Monoid instances must satisfy the semigroup law and 2 additional laws:

  • left identity: 'forall a. append(zero, a) == a'
  • right identity: 'forall a. append(a, zero) == a'

which translates to:

scala> Monoid[Int].append(Monoid[Int].zero, 1) == Monoid[Int].append(1, Monoid[Int].zero)
res2: Boolean = true

What can we do with Monoids?

  • parallel computation,
  • build complex calculations from small pieces by combining them

When you look for a Monoid, you'll find it everywhere!

import scalaz._
import Scalaz._

scala> Monoid[Int].append(1, 2)
res0: Int = 3

scala> Monoid[Int].multiply(20, 5)
res1: Int = 100

scala> Monoid[Int].isMZero(0)
res2: Boolean = true

scala> Monoid[Int].isMZero(1)
res3: Boolean = false

scala> Monoid[String].zero
res4: String = ""

scala> Monoid[Int].zero
res5: Int = 0

scala> Monoid[List[Int]].zero
res6: List[Int] = List()

scala> Monoid[Map[String, String]].zero
res7: Map[String,String] = Map()

scala> Monoid[String].multiply("a", 10)
res8: String = aaaaaaaaaa

'About Those Monoids' by Eugene Yokota

As you can see from the examples above, a Monoid is something that:

  • has a binary operation; so it is something like addition or multiplication,
  • the operands of the operation is of the same type; 1 + 1 where the (1) is an operand and both operands are of the same type,
  • the operation returns a value of the same type as the operands; so 1 + 1 = 2, the operation returns the value (2) which is of the same type as the operands.
  • the monoid has a property called 'zero' that is of the same type as the operands and the return value but the value of zero is chosen so that, when applying the operation on the operands, and one of the operands equals the zero value, the return value is equal to the other operands value.

In practise it seems that both '* together with 1' ,'+ together with 0' and '++ along with List()', share some common properties:

The function takes two parameters. - The parameters and the returned value have the same type. - There exists such a value that doesn’t change other values when used with the binary function.

// multiplication
scala> 4 * 1
res0: Int = 4

scala> 1 * 9
res1: Int = 9

// list concatenation
scala> List(1, 2, 3) ++ List()
res2: List[Int] = List(1, 2, 3)

scala> List() ++ List(1, 2, 3)
res3: List[Int] = List(1, 2, 3)

// addition
scala> 0 + 1
res4: Int = 1

scala> 1 + 0
res5: Int = 1

// string concatenation
scala> "" + "foo"
res6: String = foo

scala> "foo" + ""
res7: String = foo

// set with their union
scala> Set(1, 2, 3) ++ Set(2,4)
res8: scala.collection.immutable.Set[Int] = Set(1, 2, 3, 4)

scala> true && true
res9: Boolean = true

scala> true && false
res10: Boolean = false

scala> false && true
res11: Boolean = false

It doesn’t matter if we do (3 * 4) * 5 or 3 * (4 * 5). Either way, the result is 60. The same goes for ++. We call this property associativity. * is associative, and so is ++, but -, for example, is not.

The property associativity can be defined in a rule and we can use ScalaCheck to create property objects that we can use to test it:

// we must first import the scalacheck classes
scala> import org.scalacheck._
import org.scalacheck._

// we must define a Generator because we will generate some numbers
// and apply the assertion with random numbers to test whether
// or not the invariant holds:
scala> val numbers = Gen.chooseNum(Long.MinValue, Long.MaxValue)
numbers: org.scalacheck.Gen[Long] = org.scalacheck.Gen$$anon$1@129c7443

// we define the association rule in a method
scala> def associationRule(x: Long, y: Long, z: Long): Boolean = x * (y * z) == (x * y) * z
associationRule: (x: Long, y: Long, z: Long)Boolean

// we will create an object called a 'Property' or 'Prop' for short, and we need
// another import for that
scala> import org.scalacheck.Prop.forAll
import org.scalacheck.Prop.forAll

// here we use some Scala syntactic sugar to convert the
// associationRule method to a function of (Long, Long, Long) => Boolean
//
// The forAll() methods needs three generators, so three times our
// numbers generator so the function can be applied
scala> forAll(numbers, numbers, numbers)(associationRule)
res0: org.scalacheck.Prop = Prop

// of course we can write it all in explicit style:
scala> forAll(numbers, numbers, numbers)((x: Long, y: Long, z: Long) => associationRule(x, y, z))
res1: org.scalacheck.Prop = Prop

// we can now test the property
scala> res1.check
+ OK, passed 100 tests.

So what is a Monoid?

A monoid is when you have an associative binary function and a value which acts as an identity with respect to that function.

The binary operation that the monoid supports is append or symbolic |+|, which It takes two values of the same type and returns a value of that type.

The identity value of Monoid is called zero in Scalaz:

scala> Monoid[Int].zero
res0: Int = 0

// sure enough, when applying the zero of the Monoid to the 'plus' operation
// we get a '1' back, which holds true for every value?
scala> 1 + Monoid[Int].zero
res1: Int = 1

import org.scalacheck._
import org.scalacheck.Prop.forAll

scala> val numbers = Gen.chooseNum(Long.MinValue, Long.MaxValue)
numbers: org.scalacheck.Gen[Long] = org.scalacheck.Gen$$anon$1@63d2f26c

scala> forAll(numbers)(x => x + Monoid[Long].zero == x)
res2: org.scalacheck.Prop = Prop

// yes it does!
scala> res2.check
+ OK, passed 100 tests.

Monoids and Multiplication

Scalaz returns a Monoid with a zero for addition when we use the Monoid[Long] summoner, and that zero is of value '0', but for multiplication we need an identity value of 1. How do we instruct Scalaz to return a Monoid for multiplication?

We must use a Tagged type:

scala> Monoid[Long @@ Tags.Multiplication].zero
res0: scalaz.@@[Long,scalaz.Tags.Multiplication] = 1

// so we can do the following multiplication
scala> Tags.Multiplication(10) |+| Monoid[Int @@ Tags.Multiplication].zero
res1: scalaz.@@[Int,scalaz.Tags.Multiplication] = 10

// note, when we want to use the 'addition' Monoid we just write
// so we must wrap the left operand in a Tags.Multiplication when we
// want to do multiplication, else we dont.
scala> 10 |+| Monoid[Int].zero
res2: Int = 10

We can now do the following:

// 'add' stuff together using a Monoid that does multiplication
scala> def add[A](xs: List[A @@ Tags.Multiplication])(implicit m: Monoid[A @@ Tags.Multiplication]): A @@ Tags.Multiplication = xs.foldLeft(m.zero)(m.append(_, _))
add: [A](xs: List[scalaz.@@[A,scalaz.Tags.Multiplication]])(implicit m: scalaz.Monoid[scalaz.@@[A,scalaz.Tags.Multiplication]])scalaz.@@[A,scalaz.Tags.Multiplication]

// of course, when we call the method, it needs elements not of 'A' but of 'A @@ Multiplication' so we need to map
// all the elements
scala> add(List(1, 2, 3, 4).map(Tags.Multiplication(_)))
res0: scalaz.@@[Int,scalaz.Tags.Multiplication] = 24

// note that the result is tagged as Multiplication so we
// now that it is the result of a Multiplication
// We can unwrap the tagged value:
scala> val x: Int = Tag.unwrap(res0)
x: Int = 24

When you think about it, the Tag or '@@' annotation doesn't let us 'plug-in' the wrong Monoid in our computation. If we could just replace an 'Addition' Monoid with a 'Multiplication' Monoid, we could accidentally get the wrong result and as you see, the result wouldn't say anyting about the computation because with normal computation, the type doesn't tell us anything about the computation.

I think the solution of Scalaz makes the computation explicit and I like that approach.

Calculating factorials

In mathematics, the factorial of a non-negative integer n, denoted by n!, is the product of all positive integers less than or equal to n. For example:

5! = 5 * 4 * 3 * 2 * 1 = 120

Note: 0! means an 'empty product' and is by convention always equal to '1', so '0! == 1'

Some factorials:

0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720

We can calulate a factorial with our Multiplication Monoid when the contents of the collection contains all the elements of the factorial so (1, 2, 3, 4, 5) without duplications:

scala> def factorial[A](xs: List[A @@ Tags.Multiplication])(implicit m: Monoid[A @@ Tags.Multiplication]): A @@ Tags.Multiplication = xs.foldLeft(m.zero)(m.append(_, _))
factorial: [A](xs: List[scalaz.@@[A,scalaz.Tags.Multiplication]])(implicit m: scalaz.Monoid[scalaz.@@[A,scalaz.Tags.Multiplication]])scalaz.@@[A,scalaz.Tags.Multiplication]

scala> factorial(List(1, 2, 3, 4, 5).map(Tags.Multiplication(_)))
res1: scalaz.@@[Int,scalaz.Tags.Multiplication] = 120

Calculating factorials can be done using recursion

def factorial(n: Int): Int =
  if(n == 0) 1
  else n * factorial(n - 1)

scala> factorial(5)
res0: Int = 120

// we can also use pattern matching
def factorial(n: Int): Int = n match {
  case 0 => 1
  case _ => n * factorial(n - 1)
}

scala> factorial(5)
res1: Int = 120

// factorial tail recursive

def factorial(accumulator: Int, number: Int): Int = {
  if(number == 1) accumulator
  else factorial(number * accumulator, number - 1)
}

factorial(1, 5)

// factorial using a fold
scala> List(5, 4, 3, 2, 1).foldLeft(1)(_ * _)
res1: Int = 120

Monoids and fold

Monoids assure that the collection we want to fold doesn't need to guarantee order.

Monoids and foldable collections

A Monoid can be used with the standard scala collections that support the foldLeft and foldRight functions for example Seq, List, Set, Vector, Array, Map[K, V]. The foldLeft operation is left associative and foldRight operation is right associative.

Of course, Scalaz provides the scalaz.Foldable type class and Foldable type class instances that beside the foldLeft and foldRight methods, provides convenience methods that implicitly uses a Monoid to aggregate results, which makes folding structures less verbose.

Because the monoid is associative, you get the same result whether you use foldLeft or foldRight.

// without using the Monoid, we must select an operation that we know is associative:
scala> List(1, 2, 3).foldLeft(0)(_ + _)
res0: Int = 6

// with the Monoid, we don't have to worry about the rules, when there is a Monoid of the supporting type
// then its proven to be associative (well, when you have tested the Monoid with the Monoid rules that is...)
// so we can just apply it on our collection
scala> List(1, 2, 3).foldLeft(Monoid[Int].zero)(Monoid[Int].append(_, _))
res1: Int = 6

// we can even define a generic fold method that can fold every 'A',
// when there is a Monoid[A] type class instance
scala> def fold[A](xs: List[A])(implicit m: Monoid[A]): A = xs.fold(m.zero)(m.append(_, _))
fold: [A](xs: List[A])(implicit m: scalaz.Monoid[A])A

scala> fold(List(1, 2, 3))
res2: Int = 6

// the type class Foldable provides a 'fold' method that does just that.
// Foldable 'folds' structures eg. a List structure to an A when there is a
// Monoid of A:
scala> Foldable[List].fold(List(1, 2, 3))
res3: Int = 6

// we can rewrite our generic fold method to:
// we need the context-bound syntax to get a Monoid[A] that will be used by
// the Foldable[A]
def fold[A: Monoid](xs: List[A])(implicit f: Foldable[A]): A = f.fold(xs)

scala> fold(List(1, 2, 3))
res4: Int = 6

// we can even make it more generic and fold every shape
// where we have a foldable for and a Monoid for the element type
scala> def fold[F[_], A: Monoid](xs: F[A])(implicit f: Foldable[F]): A = f.fold(xs)
fold: [F[_], A](xs: F[A])(implicit evidence$1: scalaz.Monoid[A], implicit f: scalaz.Foldable[F])A

scala> fold(List(1, 2, 3))
res5: Int = 6

scala> fold(Set(1, 2, 3))
res6: Int = 6

scala> fold(Vector(1, 2, 3))
res7: Int = 6

// Foldable lets us also map-and-combine in one step using a Monoid that should be available:

scala> List(1, 2, 3).foldMap(_ + 1)
res8: Int = 9

// or calling the Foldable type class explicit
scala> Foldable[List].foldMap(List(1, 2, 3))(_ + 1)
res9: Int = 9

Equal

scalaz.Equal: A type safe alternative to universal equality.

import scalaz._
import Scalaz._

scala> Equal[Int].equal(1, 1)
res0: Boolean = true

scala> Equal[String].equal("1", "2")
res1: Boolean = false

scala> Equal[String].equal("foo", "foof")
res2: Boolean = false

// the following test for equality
// using the standard 'double-equals-symbol'
// is an untyped equality test
scala> List(1, 2, 3) == "a"
res3: Boolean = false

// scalaz provides the '===' or 'triple-equals-symbol' that is
// a type-safe alternative to the standard 'double-equals-symbol'

scala> List(1, 2, 3) === "a"
<console>:28: error: type mismatch;
 found   : String("a")
 required: List[Int]
       List(1, 2, 3) === "a"

// The compiler agrees that testing a 'List' and 'String' for equality
// makes no sense and shouldn't even compile.

scala> List(1, 2, 3) === List(1, 2, 3)
res4: Boolean = true

Order

scalaz.Order:

import scalaz._
import Scalaz._

scala> Order[Int].min(1, 2)
res0: Int = 1

scala> Order[Int].min(2,1)
res1: Int = 1

scala> Order[Int].order(1, 2)
res2: scalaz.Ordering = LT

scala> Order[Int].order(2, 1)
res3: scalaz.Ordering = GT

scala> Order[Int].order(2, 2)
res4: scalaz.Ordering = EQ

scala> Order[Int].sort(2,1)
res5: (Int, Int) = (1,2)

scala> Order[Int].sort(1, 2)
res6: (Int, Int) = (1,2)

scala> Order[List[Int]].order(List(1, 5, 2), List(1, 2, 5))
res7: scalaz.Ordering = GT

Show

scalaz.Show: a type class for conversion to textual representation.

import scalaz._
import Scalaz._

scala> Show[Int].show(1)
res0: scalaz.Cord = 1

scala> Show[Int].shows(1)
res1: String = 1

Foldable

scalaz.Foldable: provides ways to combine the elements of a list to a new result.

// looks for an implicit Monoid[Int] that defines the 'zero' and then folds the List[Int]

scala> Foldable[List].fold(List(1, 2, 3, 4))
res0: Int = 10

// first maps each element using the given function, then based on a Monoid which must
// be of type of the element type so here Monoid[Int], all the elements will be combined
scala> Foldable[List].foldMap(List(1, 2, 3))(_+1)
res1: Int = 9

// using a Monoid, here Monoid[Int], a the contents will be combined
// using a right-associative fold
scala> Foldable[List].sumr(List(1, 2, 3))
res2: Int = 6

// using a Monoid, here Monoid[Int], a the contents will be combined
// using a left-associative fold
scala> Foldable[List].suml(List(1, 2, 3))
res3: Int = 6

// Foldable provides some new operations
scala> List(1, 2, 3).concatenate
res4: Int = 6

// we can also sequence effects
scala> List(1.some, 2.some, 3.some).sequence
res5: Option[List[Int]] = Some(List(1, 2, 3))

scala> def validateNumber(str: String): ValidationNel[String, Int] =
     |   str.parseInt.leftMap(_.toString).toValidationNel
validateNumber: (str: String)scalaz.ValidationNel[String,Int]

scala> List("1", "2", "3").map(validateNumber)
res6: List[scalaz.ValidationNel[String,Int]] = List(Success(1), Success(2), Success(3))

scala> List("1", "2", "3").map(validateNumber).sequenceU
res7: scalaz.Validation[scalaz.NonEmptyList[String],List[Int]] = Success(List(1, 2, 3))

// or all in one go
scala> List("1", "2", "3").traverseU(validateNumber)
res8: scalaz.Validation[scalaz.NonEmptyList[String],List[Int]] = Success(List(1, 2, 3))

scala> List("foo", "bar", "baz").traverseU(validateNumber)
res9: scalaz.Validation[scalaz.NonEmptyList[String],List[Int]] = Failure(NonEmpty[java.lang.NumberFormatException: For input string: "foo",java.lang.NumberFormatException: For input string: "bar",java.lang.NumberFormatException: For input string: "baz"])

Traverse

scalaz.Traverse: Provides operations for traversing tructures

Apply

scalaz.Apply: provides the 'app' method. Accepts a Functor and and Applicative Functor.

import scalaz._
import Scalaz._

scala> val applicativeFunctor = Option((_: Int) + 1)
applicativeFunctor: Option[Int => Int] = Some($$Lambda$2201/638605646@5648494f)

scala> Apply[Option].ap(1.some)(applicativeFunctor)
res0: Option[Int] = Some(2)

Functor

scalaz.Functor: provides a way to map over a value in a context.

import scalaz._
import Scalaz._

Functor[Option].map(1.some)(_ + 1)
res0: Option[Int] = Some(2)

scala> Functor[List].map(List(1, 2, 3))(_ + 1)
res1: List[Int] = List(2, 3, 4)

scala> Functor[NonEmptyList].map(NonEmptyList(1, 2, 3))(_ + 1)
res2: scalaz.NonEmptyList[Int] = NonEmpty[2,3,4]

Digression: A very interesting read about functors The Many Functions of Functor - PinealServo that goes beyond the meaning of Functor in Category Theory and researches the word 'functor' in other contexts and provides some insight into the history of the word.

TL;DR: The word has been coined by Rudolf Carnap (1891—1970) a German-born philosopher that first used the word in the 1934 book The Logical Syntax of Language. After being coined, the word has been reused in category theory by Saunders Mac Lane et al. in 1945, so thats ten years after Carnap coined the word.

Applicative

scalaz.Applicative: an Applicative Functor.

import scalaz._
import Scalaz._

scala> val applicativeFunctor = Option((_: Int) + 1)
applicativeFunctor: Option[Int => Int] = Some($$Lambda$2201/638605646@5648494f)

scala> Applicative[Option].ap(1.some)(applicativeFunctor)
res0: Option[Int] = Some(2)

scala> 1.some <*> applicativeFunctor
res1: Option[Int] = Some(2)

// put a value into a context
scala> Applicative[Option].point(1)
res2: Option[Int] = Some(1)

// put a value into a context
scala> Applicative[Option].pure(1)
res3: Option[Int] = Some(1)

Monad

scalaz.Monad: provides a way to compose multiple monads into one, so in that sense its a monoid, and provides a way to sequence computation where a computation is dependent on the result of the previous computation.

import scalaz._
import Scalaz._

// I advice not to use .bind but stick with using 'flatMap'
scala> Monad[Option].bind(1.some)(x => Option(x + 1))
res0: Option[Int] = Some(2)

scala> 1.some >>= (x => Option(x + 1))
res1: Option[Int] = Some(2)

// I advice using the for-yield syntax for composing monads
// and not using '>>='
scala> for {
     | x <- 1.some
     | y <- Option(x + 1)
     | } yield y
res12 Option[Int] = Some(2)

Digression:

  • Monad (from Greek 'Monas'): means 'unit' or 'alone' refers in creation theory to the first being, divinity, or the totality of all beings. The concept was reportedly conceived by the Pythagoreans and may refer variously to a single source acting alone and/or an indivisible origin. The concept was later adopted by other philosophers, such as Leibniz.

State Monad

The scalaz.State Monad provides a convient way to handle state that needs to be passed through a set of functions. You might need to keep track of results, need to pass some context around a set of functions, or require some (im)mutable context for another reason.

Manipulating a list

import scalaz._
import Scalaz._

scala> def addToList(x: Int): State[List[Int], Unit] =
     |   State[List[Int], Unit](xs => (x :: xs, ()))
addToList: (x: Int)scalaz.State[List[Int],Unit]

scala> def listMutationComposition: State[List[Int], Unit] = for {
     |   _ <- addToList(1)
     |   _ <- addToList(2)
     |   _ <- addToList(3)
     |   r <- addToList(4)
     | } yield r
listMutationComposition: scalaz.State[List[Int],Unit]

scala> val result: (List[Int], _) =
     |   listMutationComposition.run(List())
result: Tuple2[List[Int], _] = (List(4, 3, 2, 1),())

Manipulating a case class

import scalaz._
import Scalaz._

trait Event
case class NameAltered(name: String) extends Event
case class AgeAltered(age: Int) extends Event

case class Person(name: String, age: Int)

scala> def handleEvent(e: Event): State[Person, Unit] = State { person =>
     |     e match {
     |       case NameAltered(name) => (person.copy(name = name), ())
     |       case AgeAltered(age) => (person.copy(age = age), ())
     |     }
     | }
handleEvent: (e: Event)scalaz.State[Person,Unit]

scala> def manipPerson: State[Person, Unit] = for {
     |   _ <- handleEvent(NameAltered("Dennis"))
     |   _ <- handleEvent(AgeAltered(42))
     | } yield ()
manipPerson: scalaz.State[Person,Unit]

scala> manipPerson(Person("", 0))
res0: scalaz.Id.Id[(Person, Unit)] = (Person(Dennis,42),())

Processing an event log using State Monad

import scalaz._
import Scalaz._

trait Event
case class PersonCreated(name: String, age: Int) extends Event
case class NameAltered(name: String) extends Event
case class AgeAltered(age: Int) extends Event

case class Person(name: String, age: Int)

def handleEvent(e: Event): State[Option[Person], Unit] = State {
  case maybePerson => e match {
    case PersonCreated(name, age) => (Option(Person(name, age)), ())
    case NameAltered(name) => (maybePerson.map(_.copy(name = name)), ())
    case AgeAltered(age) => (maybePerson.map(_.copy(age = age)), ())
  }
}

val xs: List[Event] =
  List(
    PersonCreated("Dennis", 42),
    NameAltered("Foo"),
    AgeAltered(42),
    AgeAltered(43),
    NameAltered("Bar"),
    AgeAltered(44)
  )

scala> val (person, _) = xs.traverseS(handleEvent).run(none[Person])
person: Option[Person] = Some(Person(Bar,44))

IO Monad

Reader Monad

The Reader monad can be used to easily pass configuration (or other values) around, and can be used for stuff like dependency injection.

The Reader Monad is a monad that allows us to compose operations that depend on some input. Instances of Reader wrap up functions of one argument, providing us with useful methods for composing them.

One common use for Readers is injecting configuration. If we have a number of operations that all depend on some external configuration, we can chain them together using a Reader. The Reader produces one large operation that accepts the configuration as a parameter and runs our program in the order we specified it.

State Monad

The State Monad allows us to pass additional state around as part of a computation. We define State instances representing atomic operations on the state, and thread them together using map and flatMap. In this way we can model mutable state in a purely functional way, without using mutation.

  • get extracts the state as the result;
  • set updates the state and returns unit as the result;
  • pure ignores the state and returns a supplied result;
  • inspect extracts the state via a transformation function;
  • modify updates the state using an update function.

Writer Monad

Keep track of a sort of logging during a set of operations

The Writer Monad is a monad that lets us carry a log along with a computation. We can use it to record messages, errors, or additional data about a computation, and extract the log with the final result.

One common use for Writers is recording sequences of steps in multi-threaded computations, where standard imperative logging techniques can result in interleaved messages from different contexts. With Writer the log for the computation is tied to the result, so we can run concurrent computations without mixing logs.

Eval Monad

The Eval Monad is a monad that allows us to abstract over different models of evaluation. We typically hear of two such models: eager and lazy. Eval throws in a further distinction of memoized and unmemoized to create three models of evaluation:

  • now—evaluated once immediately (equivalent to val);
  • later—evaluated once when the value is first needed (equivalent to lazy val);
  • always—evaluated every time the value is needed (equivalent to def).

Monad Transformers

Monad transformers are builders to create monads. We use monad transformers to build monads, which we then use via the Monad type class. Thus the main points of interest when using monad transformers are:

the available transformer classes; building stacks of monads using transformers; constructing instances of a monad stack; and pulling apart a stack to access the wrapped monads.

Cake Pattern

...

Terms

  • Auto (Greek): means 'self'

  • Iso (Greek): means 'equal'

  • Homos (Greek): means 'same'

  • Endos (Greek): means 'inside'

  • Morph (Greek): means 'form' or 'shape'

  • Morphism (Greek): 'to form' or 'to shape'

  • Cata (Greek): means 'downwards' or 'according to'

  • Monad (from Greek 'Monas'): means 'unit' or 'alone' refers to the first being, divinity or the totality of all beings

  • Algebra from the Arabic word al-jabr from the book 'Hidab al-jabr wal-muqubala' by ' Mohammed ibn-Musa al-Khowarizmi' is a way of solving equations.

  • Algebraic: means 'only defined by its algebra' so the operations 'that something' supports and the laws of these operations,

  • An algebraic structure: an algebraic structure that something that is defined only by its algebra, so the operations it supports and the layws of these operations

  • Monoid: an algebraic structure that conforms to the monoid laws

  • Isomorphism: Iso='equal' and Morphism='to shape': An Isomorphism

  • Automorphism: Auto='self', Morphism='to shape': An Automorphism

  • Homomorphism: Homos='same', Morphism='to shape': Homomorphism

  • Anamorphism: Ana='upwards', morphism='to shape': used for

  • Catamorphism: Cata='downwards', Morphism='to shape': An Catamorphisms, they are known as the fold, foldRight, foldLeft on eg. the List that folds the structure down to a single value, which is a catamorphism. Of course the fold is not limited to the List datastructure but the idea is that is should work for any datastructure.

  • Endomorphism: Endon='inside', Morphism='to shape': An Endomorphism of a group is a homomorphism from one object to itself.

YouTube

Resources

Books

Other FP Libraries

Github

About

A small study project on type classes, level of entry is 'beginner'

License:Apache License 2.0


Languages

Language:Scala 100.0%