26-Oct-11
Ittay Dror
@ittayd
http://www.tikalk.com/blogs/ittayd
Functional Programming
From OOP perspective
2 WWW.TIKALK.COM
What is FP?
3 WWW.TIKALK.COM
“a programming paradigm that
treats computation as the
evaluation of mathematical
functions and avoids state and
mutable data”
4 WWW.TIKALK.COM
• Functions as values • Immutable data structures • Mathematical models • Referential transparency
5 WWW.TIKALK.COM
Why Should We Care?
6 WWW.TIKALK.COM
Not everything is an
object
7 WWW.TIKALK.COM
service.init val data = service.get service.close println(data)
May Forget
Nothing to return?
May forget / call twice
8 WWW.TIKALK.COM
IOC
9 WWW.TIKALK.COM
class Service { def withData[T](f: Data => T) { init if (thereIsData) f(getData) close } }
service.witData(println)
10 WWW.TIKALK.COM
Simplification
11 WWW.TIKALK.COM
In OOP: trait DataConsumer[T] { def withData(data: Data): T {
In FP:
Data => T
12 WWW.TIKALK.COM
Factories are partially
applied functions
13 WWW.TIKALK.COM
val factory_: File => String => User = file => name => readFromFile(name) val factory: String => User = factory_(usersFile)
Look ma, no interfaces
14 WWW.TIKALK.COM
Security
15 WWW.TIKALK.COM
Immutable data is
sharable
16 WWW.TIKALK.COM
Instead of modifying,
create new
17 WWW.TIKALK.COM
Abstraction
18 WWW.TIKALK.COM
Functors, Applicatives,
Monads
19 WWW.TIKALK.COM
Usual way of describing:
WTF??
20 WWW.TIKALK.COM
OO way: Design Patterns
21 WWW.TIKALK.COM
Values in a context
22 WWW.TIKALK.COM
Option[X]
List[X]
Future[X]
23 WWW.TIKALK.COM
trait Future[A] { def get: A {
def findUser(name: String): Future[User] = … … val future = findUser(userName) val user = future.get println(user)
24 WWW.TIKALK.COM
val future = findUser(userName) future.onReady{user => println(user)}
trait Future[A] { def onReady(f: A => Unit) }
25 WWW.TIKALK.COM
Functor
26 WWW.TIKALK.COM
trait Future[A] { def map[B](f: A => B): Future[B] {
val user = findUser(userName) val age: Future[Int] = user.map{user => user.age}
27 WWW.TIKALK.COM
list.map(compute)
Vs.
val result = new ArrayList(list.size) for (i <- 0 to (list.size - 1)) { result += compute(list(i)) } result
28 WWW.TIKALK.COM
list.par.map(compute)
Vs.
class ComputeTask extends RecursiveTask { def compute = // split and call invokeAll... { val pool = new ForkJoinPool() val task = new ComputeTask() pool.invoke(task)
29 WWW.TIKALK.COM
trait Future[A] { def get: A def map[B](f: A => B) = new Future[B] { def get = f(Future.this.get)
{ {
30 WWW.TIKALK.COM
def marry(man: User, woman: User): Family = ⠄⠄⠄ val joe = findUser("joe") val jane = findUser("jane")
Get Joe and Jane married
31 WWW.TIKALK.COM
joe.map{joe => jane.map{jane => marry(joe, jane)}}
User => Family
Future[Family]
User => Future[Family]
Future[Future[Family]]
32 WWW.TIKALK.COM
trait Future[A] { def apply[B, C](other: Future[B], f: (A, B) => C): Future[C] }
joe.apply(jane, marry)
Attempt I
33 WWW.TIKALK.COM
It is easier to work with
single argument functions
34 WWW.TIKALK.COM
Curried Functions
val func: Int => Int => Int = a => b => a + b
(marry _).curried
35 WWW.TIKALK.COM
trait Future[A] { ... def apply[B](f: Future[A => B]): Future[B] {
Attempt II
36 WWW.TIKALK.COM
joe.apply(jane.map((marry _).curried))
User => User => Family
Future[User => Family]
User => Future[Family]
37 WWW.TIKALK.COM
We can do better
38 WWW.TIKALK.COM
val marry: Future[User => User => Family] = Future(marry) val partial: Future[User => Family] = futureMarry.apply(joe) val family: Future[Family] = partial.apply(jane)
Future(marry).apply(joe).apply(jane) Future(marry)(joe)(jane)
Using apply compiles iff A is a function
39 WWW.TIKALK.COM
Applicative Functors
40 WWW.TIKALK.COM
• Put value in context • Apply function in a context to value in a context
41 WWW.TIKALK.COM
trait Future[A] { def apply[B, C](b: Future[B]) (implicit ev: A <:< (B => C)) : Future[C] {
object Future { def apply[A](a: A): Future[A] }
Future((marry _).curried)(joe)(jane)
42 WWW.TIKALK.COM
trait Future[A] { def apply[B, C](b: Future[B]) (implicit ev: A <:< (B => C)) = new Future[C] { def get = Future.this.get(b.get)
{ {
object Future { def apply[A](a: A) = new Future[A] { def get = a } } }
43 WWW.TIKALK.COM
Composing Functions that
Return Value in a Context
44 WWW.TIKALK.COM
Composing Special
Functions
def findUser(name: String): Future[User] def findProfile(user: User): Future[Profile]
findProfile(findUser("joe"))
45 WWW.TIKALK.COM
Monads
46 WWW.TIKALK.COM
trait Future[A] { ... def flatMap[B](f: A => Future[B]): Future[B] }
findUser("joe").flatMap(findProfile)
47 WWW.TIKALK.COM
trait Future[A] { ... def flatMap[B](f: A => Future[B]): Future[B] = new Future[B] { def get = f(Future.this.get).get } } }
48 WWW.TIKALK.COM
All Together
trait Context[A] { def map[B](f: A => B): Context[B] def apply[B, C](b: Context[B]) (implicit ev: A <:< (B => C)): Context[C] def flatMap[B](f: A => Context[B]): Context[B] }
49 WWW.TIKALK.COM
Laws
50 WWW.TIKALK.COM
Idiomatic “interface” for
context classes
51 WWW.TIKALK.COM
Monoid • For type A to be (have) a Monoid:
• Binary operation „•„: (A, A) => A • Identity element „∅‟: A
52 WWW.TIKALK.COM
• Int is a Monoid (2 ways): • Binary operation: + or * • Identity element: 0 or 1
• String is a Monoid (2 ways): • Binary operation: append or prepend • Identity element: “”
53 WWW.TIKALK.COM
• Future[A] is a monoid, if A is a monoid • Binary operation: Future(A#•)
• (Future as applicative) • Identity element: Future(A#identity)
54 WWW.TIKALK.COM
Monoids generalize folds
55 WWW.TIKALK.COM
Folds: reduce values to
one
• List(x,y,z) => ∅ • x • y • z • Option(x) => if Some ∅ • x else ∅
56 WWW.TIKALK.COM
Unfolds: Create values
from initial value and
function • 1, if (a < 3) Some(a, a+1) else None
• Some(1, 2), Some(2, 3), None • ∅ • 1 • 2 • 3 • If our monoid is a List:
• Nil ++ List(1) ++ List(2) ++ List(3) • List(1,2,3)
57 WWW.TIKALK.COM
Since many things are
monoids, we can have
many generic algorithms
58 WWW.TIKALK.COM
Problems with subtyping:
• Namespace pollution • Need to control the class • Describes behavior, not „is-a‟ • Sometimes ability is conditional:
• Future is a monoid if A is a monoid • Sometimes there are different definitions
• E.g., 2 monoids for integers • Maybe a class has several facets
• E.g. context of 2 values
59 WWW.TIKALK.COM
Typeclass
• Define an API • Create instance for each class that can conform to the API
60 WWW.TIKALK.COM
Java example:
Comparator
61 WWW.TIKALK.COM
trait Functor[F[_]] { def map[A, B](fa: F[A], f: A => B): F[B] {
implicit object FutureHasFunctor extends Functor[Future] { def map[A, B](t: Future[A], f: A => B) = new Future[B] { def get = f(t.get) } } {
def age(name: String) = { val functor = implicitly[Functor[Future]] functor.map(findUser(name), {u: User => u.age}) }
62 WWW.TIKALK.COM
def age(name: String) (implicit functor: Functor[Future]) { functor.map(findUser(name), {u: User => u.age}) }
63 WWW.TIKALK.COM
def age[F[_] : Functor](name: String) { functor.map(findUser(name), {u: User => u.age}) }
64 WWW.TIKALK.COM
Generic Programming
65 WWW.TIKALK.COM
def sequence[A, AP[_] : Applicative] (apas: List[AP[A]]): AP[List[A]]
val futureOfList = sequence(listOfFutures)
66 WWW.TIKALK.COM
trait Applicative[AP[_]] { def pure[A](a: A) : AP[A] def apply[A, B](ap: AP[A => B], a: AP[A]): AP[B] {
67 WWW.TIKALK.COM
It is NOT important if you
don’t understand the next
slide
68 WWW.TIKALK.COM
def sequence[A, AP[_] : Applicative](apas: List[AP[A]]): AP[List[A]] = { val applicative = implicitly[Applicative[AP]] import applicative._ val cons = pure{x: A => xs: List[A] => x :: xs} apas match { case Nil => pure(Nil) case apa :: apas => val recursion = sequence(apas) // AP[List[A]] val partial = apply(cons, apa) // AP[List[A]=>List[A]] apply(partial, recursion) } }
69 WWW.TIKALK.COM
Beyond
70 WWW.TIKALK.COM
val m = Map[Int, Int]() m.map{case (k, v) => k + v}
71 WWW.TIKALK.COM
def map[B, That](f: A => B) (implicit bf: CanBuildFrom[Repr, B, That]) : That
72 WWW.TIKALK.COM
?