Scalaz – Or: How I learned to stop worrying and love monads



Scalaz – Or: How I learned to stop worrying and love monads

0 0


scalaz-scala-exchange


On Github noelmarkham / scalaz-scala-exchange

  • The point of this talk is to introduce Scalaz to those who are unfamiliar, daunted/afraid of its strange syntax, hopefully can find use for this in your own projects

Scalaz

Or: How I learned to stop worrying and love monads

Scala Exchange - 8 December 2014

Noel Markham - @noelmarkham

  • Explain why I am qualified to talk about Scalaz
  • Next slide: Schedule

Hello :-)

  • Last element: Monads
  • Next slide: SBT/Imports

Schedule

  • Enhancements out of the box
  • A better either
  • Typeclass definition
  • Various Scalaz typeclasses
  • Monads
  • Next slide: Out of the box/booleans
"org.scalaz" %% "scalaz-core" % "7.1.0"
          
import scalaz._
import Scalaz._
          
  • Last element: Comparing to if statement
  • Next slide: Strings/Parse primitives

Enhancements out of the box

Booleans

.option

scala> (6 < 10).option("corrie")
res0: Option[String] = Some(corrie)

scala> (6 > 10).option("emmerdale")
res1: Option[String] = None
	  

Neater than:

scala> if(6 < 10) Some("corrie") else None
res2: Option[String] = Some(corrie)
	  
  • Next slide: Lists: allPairs

Enhancements out of the box

Strings

.parseXXX

scala> val i = "6".parseInt
i: Validation[
     NumberFormatException,Int] = Success(6)
          
scala> val b = "corrie".parseBoolean
b: Validation[
     IllegalArgumentException,
     Boolean] =
  Failure(IllegalArgumentException:
                 For input string: "corrie")
          
  • Next slide: Lists: powerset

Enhancements out of the box

Lists

.allPairs

> List(1, 2, 3).allPairs.foreach(println)
(1,2)
(2,3)
(3,4)
(1,3)
(2,4)
(1,4)
          
  • Next slide: All types

Enhancements out of the box

Lists

.powerset

> List('a','b','c').powerset.foreach(println)
List(a, b, c)
List(a, b)
List(a, c)
List(a)
List(b, c)
List(b)
List(c)
List()
          
  • Last element: 1.some |+| 2.some
  • Next slide: .some on own Money case class

Enhancements out of the box

All types

.some

scala> "corrie".some
res6: Option[String] = Some(corrie)
          
scala> Some("emmerdale")
res7: Some[String] = Some(emmerdale)
          
scala> Some(1) |+| Some(2)
error: value |+| is not a member of Some[Int]
              Some(1) |+| Some(2)
          
scala> 1.some |+| 2.some
res12: Option[Int] = Some(3)
          
  • Next slide: There are plenty more screenshot

Enhancements out of the box

All types

.some

> case class Money(ccy: String, amount: Int)
defined class Money

> Money("EUR", 3).some
res3: Option[Money] = Some(Money(EUR, 3))
          
  • Next slide: A better either

Enhancements out of the box

There are plenty more.

  • Last element: ApplicationError or String
  • Next slide: Right projections

A Better Either

Scala: Either[A, B]

Scalaz: \/[A, B]

Infix notation: A \/ B

Exception \/ HttpResponse

ApplicationError \/ String

  • Works just like an option
  • Last element: more.map
  • Next slide: But can't we give it a better name...

A Better Either

“Biased” to the right

scala> val res = Right(6)
res: scala.util.Right[Nothing,Int] = Right(6)

scala> res.right.map(_ + 4)
res30: Either[Nothing,Int] = Right(10)

          
scala> val more = \/-(6)
more: scalaz.\/-[Int] = \/-(6)

scala> more.map(_ + 4)
res31: scalaz.\/[Nothing,Int] = \/-(10)
          
  • Works just like an option
  • Last element: Type alias
  • Next slide: Typeclasses example

A Better Either

“Can we give it a plain text name rather than that strange symbol?”

“It's just a mathematical symbol like plus.”

“Use it for a week and we can discuss it after that.”

type Result[+A] = ApplicationError \/ A

  • Next slide: Typeclass for defining order

Typeclasses

A simple example

(This example lifted from Martin Odersky's paper: Type Classes as Objects and Implicits)

  • Last element: intOrd implementation
  • Next slide: Use it with a sort method

Typeclasses

A simple example
trait Ord[T] {
 def compare(a: T, b: T):Boolean
}
          

An Ord instance for a specific type:

object intOrd extends Ord[Int] {
 def compare(a: Int, b: Int):Boolean = a <= b
}
          
  • Last element: sorted with intOrd parameter
  • Next slide: Make the ordering implicit

Typeclasses

This instance can be used when necessary:

def sort[T](xs: List[T])
           (ord: Ord[T]): List[T] = ...
            
scala> sort(List(3, 2, 1))(intOrd)
res5: List[Int] = List(1, 2, 3)
            
  • Explain here we've been able to retrofit an interface
  • Last element: Sorted with implicits
  • Next slide: No implicit in scope CHANGE

Typeclasses

A simple example
// implementation as before
implicit object intOrd extends Ord[Int] ...
          
// implementation as before
def sort[T](xs: List[T])
           (implicit ord: Ord[T]):List[T]
          
scala> sort[Int](List(4, 3, 6, 1, 7))
res4: List(1, 3, 4, 6, 7)
          
  • Last element: Compiler error CHANGE
  • Next slide: Typeclasses in Java CHANGE

Typeclasses

A simple example

No implicit in scope:

scala> sort[String](List("z", "y", "x", "w"))
<console>:28: error: 
    could not find implicit value
    for parameter ord: Ord[String]
          
  • Next slide: Typeclasses provided by Scalaz

Typeclasses

Daunted? Confused?
public interface Comparator<T> {
    int compare(T o1, T o2);
}
          

Collections.sort:

public static <T> void sort(List<T> list,
                      Comparator<? super T> c)
          
  • Next slide: Equal typeclass

Typeclasses

Within Scalaz:

  • Order
  • Equal
  • Functor
  • Monoid
  • Monad
  • ...
  • Last element: Compiler error
  • Next slide: Implement own Equal

Typeclasses

Equal
scala> "Hello" === "olleH".reverse
res13: Boolean = true
          
scala> "six" === 6
          
<console>:20: error: type mismatch;
 found   : Int(6)
 required: String
              "six" === 6
                        ^
          
  • Last element: Using ===
  • Next slide: equalA/case class/a class with equality defined as you want

Typeclasses

Equal
class Money(val ccy: String, val amount: Int)
          
implicit val equalMoney: Equal[Money] =
 new Equal[Money] {
  def equal(m1: Money, m2: Money): Boolean = {
      m1.ccy === m2.ccy && 
      m2.amount === m2.amount
  }
 }
          
new Money("GBP", 3) === new Money("EUR", 3)
          
  • Next slide: Functor

Typeclasses

Equal
case class Money(ccy: String, amount: Int)
          
implicit val equalMoney: Equal[Money] =
                           Equal.equalA[Money]
          
  • Last element: f.foreach(println)
  • Next slide: Using a functor

Typeclasses

Functor

A Functor is something that can be mapped

scala> List(10, 20, 30).map(_ / 10)
res13: List[Int] = List(1, 2, 3)
          
scala> "hello".some.map(_.length)
res14: Option[Int] = Some(5)
          
scala> val f = Future(200).map(_ === 404)

scala> f.foreach(println)
false
          
  • DESCRIBE F[_]!!!!
  • Last element: f.foreach(println)
  • Next slide: Monoid

Typeclasses

Functor
def addInt[F[_]]
        (i: Int, toAdd: F[Int])
        (implicit f: Functor[F]): F[Int] = {
  f.map(toAdd)(_ + i)
}
          
scala> addInt(6, 10.some)
res1: Option[Int] = Some(16)
          
scala> addInt(2, List(10, 11, 12, 13))
res2: List[Int] = List(12, 13, 14, 15)
          
scala> val f = addInt(100, Future(1))
scala> f.foreach(println)
101
          
  • We can lift a function
  • Last element: maybeTotal
  • Next slide: Applicative with None

Typeclasses

Applicative
> def sum(a: Int, b: Int, c: Int): Int = 
                                   a + b + c
> val m = Map("a" -> 4, "b" -> 5, "c" -> 6)
          
> val maybeA = m.get("a")
maybeA: Option[Int] = Some(4)
> val maybeB = m.get("b")
maybeB: Option[Int] = Some(5)
> val maybeC = m.get("c")
maybeC: Option[Int] = Some(6)
          
> val maybeTotal = 
       (maybeA |@| maybeB |@| maybeC)(sum)
maybeTotal: Option[Int] = Some(15)
          
  • Last element: Not just option
  • Next slide: Monoid

Typeclasses

Applicative
> val maybeD = m.get("d")
maybeD: Option[Int] = None

> val newTotal = 
   (maybeB |@| maybeC |@| maybeD)(sum)
newTotal: Option[Int] = None
          

Think of this as map for a function with an arbitrary number of arguments

Of course, any Applicative, not just Option

  • Last element: Monoid definition
  • Next slide: Associative definiton

Typeclasses

Monoid
trait Semigroup[F] {
  def append(f1: F, f2: => F): F

  def |+|(f1: F, f2: => F): F = append(f1, f2)
}
          
trait Monoid[F] extends Semigroup[F] {
  def zero: F
}
          

A Monoid is a structure with an associative binary operation and an identity element

  • THIS SLIDE GIVES EXAMPLES!
  • "However plus is defined for that specific type"
  • Integer is defined as plus rather than multiplication by default
  • Last element: String concatenation
  • Next slide: Identity definition

Typeclasses

Monoid
Associative Binary Operation
a + (b + c) is equal to (a + b) + c

Addition

5 + (6 + 7) === (5 + 6) + 7
          

Multiplication

10 * (2 * 5) === (10 * 2) * 5
          

String concatenation

"abc".concat("def".concat("ghi")) ===
          ("abc".concat("def")).concat("ghi")
          
  • What about a monoid for booleans?
  • Last element: Integer.min
  • Next slide: Monoid foldMap

Typeclasses

Monoid
Identity

Addition: zero

6 + 0 === 6
          

Multiplication: one

6 * 1 === 6
          

String concatenation: empty string

"corrie".concat("") === "corrie"
          

The identity operation for Integer.min?

Integer.min(a, Integer.MAX_VALUE) === a
          
  • Last element: List alpha beta gamma
  • Next slide: Using append

Typeclasses

Monoid

foldMap

scala> List(10, 9, 8).foldMap(i => i)
res20: Int = 27
          
scala> List("A", "BB").foldMap(_.length)
res22: Int = 3
          
  • Say how the option monoid is derived
  • Last element: Append with options
  • Next slide: Using append with maps

Typeclasses

Monoid

Using append

scala> 1 |+| 2 |+| 3
res23: Int = 6
          
scala> "Hello".some |+| None |+| "World".some
res18: Option[String] = Some(HelloWorld)
          
  • Last element: Flattened map
  • Next slide: Map append with int keys

Typeclasses

Monoid

Using append

scala> val m1 = Map(1 -> List("a", "b"),
     |              2 -> List("aa", "bb"))

scala> val m2 = Map(1 -> List("z"),
     |              3 -> List("yyy", "zzz"))

scala> m1 |+| m2
          
res25: Map(1 -> List(a, b, z),
           3 -> List(yyy, zzz),
           2 -> List(aa, bb))
          
  • Say that foldMap is not just on List, is on option and others too
  • Next slide: Monads (everyone's favourite topic)

Typeclasses

Monoid

Using append

scala> val m1 = Map("a" -> 1, "b" -> 1)

scala> val m2 = Map("a" -> 1, "c" -> 1)

scala> m1 |+| m2
          
res30: Map(a -> 2, c -> 1, b -> 1)
          
scala> List("a", "b", "b", "b", "c", "c").
     | foldMap(c => Map(c -> 1))
          
res32: Map(b -> 3, a -> 1, c -> 2)
          
  • Next slide: My experience with monads

Monads

  • Last element: Abstraction is killer feature
  • Next slide: My definition

Typeclasses

Monad

My experience with monads:

  • They're not as confusing as the Internet seems to think.
  • There are plenty of silly analogies on the Internet.
  • The name of for comprehensions is confusing.
  • The List monad is quite a confusing place to start.
  • If you can define your API to be functions A => M[B], most other things slot into place easily.
  • Abstraction over monad types is the killer feature.
  • A monad encapsulates a specific pattern that occurs so frequently in programming and functional programming generally
  • Next slide: Examples

A monad encapsulates a specific pattern that occurs frequently

  • Last element: divide function
  • Next slide: Future example

What is a monad?

Examples

Get two numbers from a map and divide one by the other

Retrieve a number from a map (but it might not be there)

def get(key: A): Option[B]
          

Perform division (but you might try to divide by zero)

def divide(num: Int, denom: Int): Option[Int]
          
  • Last element: persist function
  • Next slide: We've spotted a pattern

What is a monad?

Examples

Get some data from a web page and persist it to a database

Make an HTTP call (eventually)

def httpGet(url: String): Future[String]
          

Store a value in a database (eventually)

def persist(data: String): Future[Unit]
          
  • Last element: is this useful?
  • Next slide: Monad trait implementation

What is a monad?

There is a pattern here:

def get(key: A): Option[B]
def divide(num: Int, denom: Int): Option[Int]
          
def httpGet(url: String): Future[String]
def persist(data: String): Future[Unit]
          

All return some type with some auxiliary behaviour

Is this useful? Can we abstract this?

  • Point and bind are abstract
  • Next slide: Point function

Typeclasses

Monad
trait Monad[M[_]] { self =>
 def point[A](a: => A): M[A]
 def bind[A,B](fa: M[A])(f: (A) => M[B]): M[B]

 def flatMap[A,B](f:A => M[B]) = bind(self)(f)
 def >>=[A,B](f: A => M[B]) = bind(self)(f)

 def map[A,B](fa: M[A])(f: A => B) =
                  bind(fa)(a => point(f(a)))
}
          
  • Default minimal context
  • Last element: Given an...
  • Next slide: Bind function

Typeclasses

Monad

def point[A](a: => A): M[A]

“Given an A, this will give me an M[A].”

  • Say how this would chain, "If I had a M[B] and a B => M[C]" etc
  • You'll probably recognise this as flatMap
  • Last element: then I can use this...
  • Next slide: Chaining calls

Typeclasses

Monad

def bind[A,B](fa: M[A])(f: (A) => M[B]): M[B]

“If I have an M[A],

and a function A => M[B],

then I can use this to get M[B].”

  • Basically removes the differing behaviour
  • Last element: >>=
  • Next slide: For comprehensions

“Chaining” calls

Given:

def getUserId(username: String): Option[Int]
def getUser(id: Int): Option[User]
def getAddress(user: User): Option[Address]
          

You can chain these together using flatMap.

def addressFromUsername(username: String) =
      getUserId(username)
        .flatMap(getUser)
        .flatMap(getAddress)
          
getUserId(username) >>= getUser >>= getAddress
          
  • Last element: As long as map and flatMap are defined
  • Next slide: Different monads, different behaviour

“Chaining” calls

For comprehensions
def getUserId(username: String): Option[Int]
def getUser(id: Int): Option[User]
def getAddress(user: User): Option[Address]
          
for {
  userId  <- getUserId(username)
  user    <- getUser(userId)
  address <- getAddress(user)
} yield address
          

As long as map and flatMap are defined on the class.

  • Last element: Id monad ???
  • Next slide: Abstraction over monads/ficticious scenario

Different monads, different behaviour:

  • Option: The ability to fail fast
  • \/: The ability to fail fast, telling us about the failure
  • Future: Perform computations concurrently
  • Reader: Provide a read-only environment
  • Id: Do nothing special (???)
  • Last element: For comprehension
  • Next slide: refactoring

Abstraction over monads

Ficticious scenario:

def getUser(id: Int): Future[User]
def getAddress(user: User): Future[Address]
          
def addressFromUserId
         (userId: Int): Future[Address] = {
  for {
    user    <- getUser(userId)
    address <- getAddress(user)
  } yield address
}
          
  • Next slide: Test cases

Abstraction over monads

Refactoring:

def addressFromUserId
     (getUserFunc:    Int => Future[User],
      getAddressFunc: User => Future[Address])
     (userId: Int): Future[Address] = {
  for {
    user    <- getUserFunc(userId)
    address <- getAddressFunc(user)
  } yield address
}
          
  • Last element: assert await
  • Next slide: Using the refactoring

Abstraction over monads

Refactoring:

val userTestF: Int => Future[User] = 
      i => Future(User(i, "Bob", "Smith"))

val addrTestF: User => Future[Address] = 
      _ => Future(Address("London"))

val wiringTest =
    addressFromUserId(userTestF, addrTestF)(1)

          
assert(Await.result(wiringTest, ...
          
  • Next slide: Better test cases

Abstraction over monads

More Refactoring:

def addressFromUserId[M[_]: Monad]
          (getUserFunc:    Int => M[User],
           getAddressFunc: User => M[Address])
          (userId: Int): M[Address] = {
  for {
    user    <- getUserFunc(userId)
    address <- getAddressFunc(user)
  } yield address
}
          
  • Last element: correct address type
  • Next slide: Abstraction with point only

Abstraction over monads

More Refactoring:

val userTestF: Int => Id[User] =
                 i => User(i, "Bob", "Smith")
val addrTestF: User => Id[Address] =
                 _ => Address("London")
          
val wiringTest = addressFromUserId[Id]
                    (userTestF, addrTestF)(1)
          
assert(wiringTest, ...
          
scala> :type wiringTest
scalaz.Scalaz.Id[Address]
          
scala> val address: Address = wiringTest
address: Address = Address(London)
          
  • Last element: runLongCalc[Id]
  • Next slide: Thank you

Abstraction over monads

def expFunc(a: Int, b: Int): Future[Int] = {
  Future { /* expensive calculating here */ }
}
          
def expFunc[M[_]](a: Int,b: Int)
           (implicit m: Monad[M]):M[Int] = {
  m.point { /* expensive calculating here */ }
}
          
scala> expFunc[Future](10, 5)
res35: scala.concurrent.Future[Int] = ...

scala> expFunc[Id](10, 5)
res34: scalaz.Scalaz.Id[Int] = ...
          

Thank you

Useful links

Noel Markham

Slides: http://noelmarkham.github.io/scalaz-scala-exchange
0