Scala Exchange - 8 December 2014
Noel Markham - @noelmarkham
"org.scalaz" %% "scalaz-core" % "7.1.0"
import scalaz._
import Scalaz._
.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)
.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")
.allPairs
> List(1, 2, 3).allPairs.foreach(println)
(1,2)
(2,3)
(3,4)
(1,3)
(2,4)
(1,4)
.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()
.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)
.some
> case class Money(ccy: String, amount: Int)
defined class Money
> Money("EUR", 3).some
res3: Option[Money] = Some(Money(EUR, 3))
There are plenty more.
Scala: Either[A, B]
Scalaz: \/[A, B]
Infix notation: A \/ B
Exception \/ HttpResponse
ApplicationError \/ String
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)
“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
(This example lifted from Martin Odersky's paper: Type Classes as Objects and Implicits)
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
}
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)
// 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)
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]
public interface Comparator<T> {
int compare(T o1, T o2);
}
Collections.sort
:
public static <T> void sort(List<T> list,
Comparator<? super T> c)
Within Scalaz:
Order
Equal
Functor
Monoid
Monad
...
scala> "Hello" === "olleH".reverse
res13: Boolean = true
scala> "six" === 6
<console>:20: error: type mismatch;
found : Int(6)
required: String
"six" === 6
^
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)
case class Money(ccy: String, amount: Int)
implicit val equalMoney: Equal[Money] =
Equal.equalA[Money]
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
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
> 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)
> 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
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
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")
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
foldMap
scala> List(10, 9, 8).foldMap(i => i)
res20: Int = 27
scala> List("A", "BB").foldMap(_.length)
res22: Int = 3
Using append
scala> 1 |+| 2 |+| 3
res23: Int = 6
scala> "Hello".some |+| None |+| "World".some
res18: Option[String] = Some(HelloWorld)
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))
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)
My experience with monads:
for
comprehensions is confusing.List
monad is quite a confusing place to start.A => M[B]
, most other things slot into place easily.A monad encapsulates a specific pattern that occurs frequently
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]
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]
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?
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)))
}
def point[A](a: => A): M[A]
“Given an A
, this will give me an M[A]
.”
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]
.”
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
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.
Option
: The ability to fail fast\/
: The ability to fail fast, telling us about the failureFuture
: Perform computations concurrentlyReader
: Provide a read-only environmentId
: Do nothing special (???)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
}
Refactoring:
def addressFromUserId
(getUserFunc: Int => Future[User],
getAddressFunc: User => Future[Address])
(userId: Int): Future[Address] = {
for {
user <- getUserFunc(userId)
address <- getAddressFunc(user)
} yield address
}
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, ...
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
}
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)
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] = ...
Noel Markham
Slides: http://noelmarkham.github.io/scalaz-scala-exchange