26
FP in Scala with adts Wonderful Machines code

Fp in scala with adts part 2

Embed Size (px)

DESCRIPTION

Function Programming in Scala. A lot of my examples here comes from the book Functional programming in Scala By Paul Chiusano and Rúnar Bjarnason, It is a good book, buy it.

Citation preview

Page 1: Fp in scala with adts part 2

FP in Scala with adtsWonderful Machines code

Page 2: Fp in scala with adts part 2

ArrowArrow are generalisation of function.It is inherent from Category and can be used for general computation.Arrow heavily use tuple (x,y) in its implementation for multiple input functions. It can also encapsulate in side-effect, and often compare with Applicative and Monad.

Page 3: Fp in scala with adts part 2

Arrowtrait Category[~>[_, _]] {

self =>

// minimum set

def id[A]: A ~> A

def compose[A, B, C](f: B ~> C, g: A ~> B): (A ~> C)

def andThen[A, B, C](f: A ~> B, g: B ~> C): (A ~> C) =

compose(g, f)

}

trait Arrow[~>[_, _]] extends Category[~>] {

self =>

//minimum set

def arr[A, B](f: A => B): A ~> B

def first[A,B,C](f: A ~> B): ((A, C) ~> (B, C))

def second[A,B,C](f: A ~> B): ((C, A) ~> (C, B)) = {

def swap[X, Y] = arr[(X, Y), (Y, X)] {

case (x, y) => (y, x)

}

compose(swap, compose(first[A, B, C](f), swap))

}

def split[A,B,C,D](f: A ~> B, g: C ~> D)

: ((A, C) ~> (B, D)) = //parallelComposition

compose(second[C, D, B](g), first[A, B, C](f))

def combine[A,B,C](f: A ~> B, g: A ~> C)

: (A ~> (B,C)) = //fanoutComposition

compose(split(f, g), arr((b: A) => (b, b)))

}

Page 4: Fp in scala with adts part 2

Arrow

Arrow as generation of function, contains our familiar combinator: compose, andThen It also has various other combinators:Like first/second/split/combine

Page 5: Fp in scala with adts part 2

Understand Arrow combinators

arr andThen first

Page 6: Fp in scala with adts part 2

Understand Arrow combinators

second split combine

Page 7: Fp in scala with adts part 2

Simple exampleval FuncArrow = new Arrow[Function1] {

def arr[A, B](f: A => B): Function1[A, B] = f

def first[A,B,C](f: A => B): (((A, C)) => (B, C)) =

(ac: (A, C)) => (f(ac._1), ac._2)

def id[A]: A => A = (a: A) => a

def compose[A, B, C](f: B => C, g: A => B): (A => C)

=

(a: A) => f(g(a))

}

def twoVarFunc(x: (Int,Int)): Int = {

x._1 + x._2

}

def eleven (x: Any): Int = {11}

def twelve (x: Any): Int = {12}

val test = FuncArrow.andThen(

FuncArrow.combine(

FuncArrow.arr(eleven),

FuncArrow.arr(twelve)

),

FuncArrow.arr[(Int, Int), Int](twoVarFunc)

)

import scala.util._

val res = test.apply(new Random(System.currentTimeMillis).nextDouble)

Page 8: Fp in scala with adts part 2

Stream example def twoVarFunc: ((Stream[Int], Stream[Int])) =>

Stream[Int] = n => {

(n._1).zip(n._2).map { n => {

( n._1 < n._2 ) match {

case true => n._1

case false => n._2

}

}}

}

implicit def Stream2Applicative[A](o: Stream[A]):

Applicative[Stream, A] = new Applicative[Stream, A]

(o) {

def unit[C](a: C): Stream[C] = Stream.continually(a)

def ap_: [B](f: Stream[A => B]): Stream[B] = {

o.zip(f).map { x => (x._2(x._1)) }

}

}

val x1: Stream[Int] = {

def loop(v: Int): Stream[Int] = v #:: loop(v + 1000)

loop(0)

}

val x2: Stream[Int] = 0 #:: 1 #:: x2.zip(x2.tail).map {

n => n._1 + n._2 }

val x3: Stream[Int] = {

def loop(v: Int): Stream[Int] = v #:: loop(v * 2)

loop(1)

}

Page 9: Fp in scala with adts part 2

Stream example (cont.)val test1 = ((threeVarFunc.curried map_: x1) ap_: x2) ap_: x3

val test2 = FuncArrow.andThen(

FuncArrow.combine[Stream[Int], Stream[Int], Stream[Int]](

FuncArrow.arr( (x: Any) => { x1 } ),

FuncArrow.arr( (x: Any) => { x3 } )

),

FuncArrow.arr[(Stream[Int], Stream[Int]), Stream[Int]](twoVarFunc)

)(Stream(0))

As you can see, applicative and arrow can both acting as the framework and building block for Stream processing (unlimited Stream is not a monad, so no monad here), we can try monad and see what happens val test3 = for {

r1 <- x1

r2 <- x2

r3 <- x3

} yield { (r1,r2,r3) }

Page 10: Fp in scala with adts part 2

Operator .*: and implicit conversionLast slide, you can see operate map_: and ap_: this is because I want to rearrange the sequence and used : , every method end with : is right associative.Also I create a implicit function here that convert Stream[A] automatically to Applicative[Stream, A] , as long as this function in scope. It is powerful, but can be dangerous and hard to read.

Page 11: Fp in scala with adts part 2

Applicative/Arrow/Monad comparison val x4 = Option(1)

val x5 = Option(2)

val x6 = Option(3)

def twoVarFunc2: ((Option[Int], Option[Int])) => Option[Int] = n => {

n._1 match {

case Some(a) => {

n._2 match {

case Some(b) => {

( a < b ) match {

case true => Some(a)

case false => Some(b)

}}

case None => None

}}

case None => None

}}

def threeVarFunc: (Int, Int, Int) => Int = {

_ + _ + _

}

Page 12: Fp in scala with adts part 2

Applicative/Arrow/Monad comparison (Cont)

val test4 = ((threeVarFunc.curried map_: x4) ap_: x5) ap_: x6

val test6 = {

for {

r1 <- x4

r2 <- x5

} yield {

if(r1 == 1) {

r1 * r2

} else {

r1 + r2

}}}

val test5 = FuncArrow.andThen(

FuncArrow.combine[Option[Int], Option[Int], Option[Int]](

FuncArrow.arr( (x: Any) => { x5 } ),

FuncArrow.arr( (x: Any) => { x6 } )

),

FuncArrow.arr[(Option[Int], Option[Int]), Option[Int]](twoVarFunc2)

)(Option(0))

Page 13: Fp in scala with adts part 2

Applicative/Arrow/Monad comparison (Cont)

They looks similar and can do similar things…What distinguish them?Build system normally have 2 phases:

1. build building blocks2. assemble build blocks

These 2 phases are normally done at different time, and possibly by different people.

Page 14: Fp in scala with adts part 2

Building BlocksApplicatives

x4x5x6

Automatically implicitly converted to Applicative

Arrow

FuncArrow.arr( (x: Any) =>

{ x5 } )

FuncArrow.arr( (x: Any) =>

{ x6 } )

FuncArrow.arr[(Option[Int], Option[Int]), Option[Int]](twoVarFunc2)

Monad

x4x5x6

Which are Monads

Page 15: Fp in scala with adts part 2

Building BlocksApplicative: Isolated, Static, Sequential appliedArrow: Connected, Static, Sequential appliedMonad: Connected, Dynamic, Sequential applied

Even you can do anything at assembling time, they are different when providing the building blocks for a system.

Page 16: Fp in scala with adts part 2

Usefulness of Arrow

Arrow was proposed as the tools for Functional Reactive Programming, but because of the clumsy Tuple and difficult syntax, a lot people use Applicative and Monad more for FRP.It can be good when you need it for certain scenarios. There is no support in Scala to simplify the Arrow syntax.

Page 17: Fp in scala with adts part 2

Arrow Familytrait ArrowChoice[~>[_, _]] extends Arrow[~>] {

def left[A,B,C](f: A ~> B): (Either[A, C] ~> Either[B,C])

def right[A,B,C](f: A ~> B): (Either[C, A] ~> Either[C,B]) = {

def swap[X,Y] = arr[Either[X, Y], Either[Y, X]] {

case Left(x) => Right(x)

case Right(y) => Left(y)

}

compose(swap, compose(left[A, B, C](f), swap))

}

def multiplex[A,B,C,D](f: A ~> B, g: C ~> D)

: (Either[A,C] ~> Either[B,D]) =

compose(right[C, D, B](g), left[A, B, C](f))

def merge[A,B,C](f: A ~> C, g: B ~> C)

: (Either[A,B] ~> C) = {

def utag[D](v: Either[D, D]): D = v match {

case Left(x) => x

case Right(y) => y}

compose(arr[Either[C,C],C](utag[C]), multiplex(f,g))

}}

Arrow with choice on the left, a bit more flexible.

trait ArrowApply[~>[_, _]] extends Arrow[~>] {

def app[B, C]: (B ~> C, B) ~> C

}

Same power as Monad, so it is not popular...

Page 18: Fp in scala with adts part 2

ComonadMonadic

A => M[B]

abstract impure computation

abstract producer

Comonadic

M[A] => B

abstract context related computation

abstract consumer

Page 19: Fp in scala with adts part 2

Comonadtrait Functor[F[_]] {

def map[A,B](fa: F[A])(f: A => B): F[B]

}

trait Monad[F[_]] extends Functor[F] {

def unit[A](a: => A): F[A]

def flatMap[A,B](ma: F[A])(f: A => F[B]): F[B]

def join[A](mma: F[F[A]]): F[A] = flatMap(mma)(la => la)

override def map[A,B](ma: F[A])(f: A => B): F[B] =

flatMap(ma)(a => unit(f(a)))

}

trait Comonad[F[_]] extends Functor[F] {

def extract[A](a: F[A]): A

def extend[A,B](la: F[A])(f: F[A] => B): F[B]

// = map(duplicate(la))(f)

def duplicate[A](ma: F[A]): F[F[A]] =

extend(ma)(mma => mma)

override def map[A,B](fa: F[A])(f: A => B): F[B] =

extend(fa)((w: F[A]) => f(extract(w)))

}

Page 20: Fp in scala with adts part 2

Unlimited stream continually calculationimport scala.language.higherKinds

val StreamComonad = new Comonad[Stream] {

def extract[A](a: Stream[A]): A = a.head

override def duplicate[A](ma: Stream[A]): Stream[Stream[A]] = {

def rec(remain: Stream[A], last: Stream[A]):Stream[Stream[A]]

= {

val cur = last :+ remain.head

cur #:: rec(remain.tail, cur)

}

Stream(ma.head) #:: rec(ma.tail, Stream(ma.head))

}

def extend[A,B](la: Stream[A])(f: Stream[A] => B): Stream[B] =

{

duplicate(la).map(f)

}

}

object test {

def average(i: Stream[Int]): Double = {

val res = i.foldLeft((0,0d))( (b,a) => (b._1 + a, b._2 + 1) )

res._1.toDouble / res._2.toDouble

}

val fibs: Stream[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { n => n._1

+ n._2 }

val test1 = StreamComonad.extend[Int, Double](fibs)(average)

}

Page 21: Fp in scala with adts part 2

Comonad

Someone like it, as it capture the model: generate next element based on all historywhich is common in multi media area (audio/video), but it also means you carry a whole history around, if not careful, someone hate it...

Page 22: Fp in scala with adts part 2

ArrowLoopabstract class LazyTuple[A,B]{

def _1: A

def _2: B

}

object LazyTuple {

def apply[A, B](a: => A, b: => B): LazyTuple[A, B] = new LazyTuple[A, B] {

def _1 = a

def _2 = b

}

}

def lazyTuple[A, B](a: => A, b: => B): LazyTuple[A, B] = new LazyTuple[A, B] {

def _1 = a

def _2 = b

}

trait ArrowLoop[~>[_, _]] extends Arrow[~>] {

def loop[A, B, C](f: LazyTuple[A,C] ~> LazyTuple[B,C] ): A ~> B

}

Require language’s lazy support, kind of recursion value.Think about a feedback circuit.

Page 23: Fp in scala with adts part 2

ArrowLoopArrowLoop can have positive feedback (diverge, never stop), can have negative feedback (converge, stop at some point, which we care more)Let us do a square root calculation based on

Page 24: Fp in scala with adts part 2

ArrowLoop (square root)val FuncArrowLoop = new ArrowLoop[Function1] {

def arr[A, B](f: A => B): Function1[A, B] = f

def first[A,B,C](f: A => B): (((A, C)) => (B, C)) =

(ac: (A, C)) => (f(ac._1), ac._2)

def id[A]: A => A = (a: A) => a

def compose[A, B, C](f: B => C, g: A => B): (A => C) =

(a: A) => f(g(a))

def loop[A, B, C](f: LazyTuple[A,C] => LazyTuple[B,C] ): A

=> B =

(a: A) => new { val bc: LazyTuple[B,C] = f(lazyTuple(a, bc.

_2)) }.bc._1

}

val squareRoot = FuncArrowLoop.loop[(Double, Double),

Double, Double => Double] {

ac => lazyTuple[Double, Double => Double](

ac._2(ac._1._1),

{ x:Double => {

val ret: Double = x - (((x * x) - ac._1._2) / (2 * x))

(abs(ret - x) < 0.0001) match {

case true => ret

case false => ac._2(ret)

}

}}

)

}

Page 25: Fp in scala with adts part 2

ArrowLoop (explanation)This is not recursive function call, it is using lazy evaluation, in fact rewrite code in some sense.Happens in heap space, and not in stack, internally in fact use Java Reflection.It is Heavy, not Fast, so use it with care, and better only use it when really needed.But it does provide many things youdo not see often.

Page 26: Fp in scala with adts part 2

Fix/MonadFixdef fix[A](f: Lazy[A => A]): A // result is where f(x) = x

trait MonadFix[F[_]] extends Monad[F] {

def mfix[A](f: Lazy[A => F[A]]): F[A]

}

Fix comes from mathematical notion of least fixpoint of function , for engineer, it is more of a abstraction of recursive function call, similar to ArrowLoop with lazy, can happen in heap space, Fix also related to Free Monad provide a way to do general stackless recursion (without heavy lifting in reflection).

MonadFix similar to Fix , it means apply the monadic effect (side effect) only once, but also recursion to converge to get final result.