Last active
August 29, 2015 14:23
-
-
Save jserranohidalgo/31e7ea8efecfb36276cf to your computer and use it in GitHub Desktop.
The essence of purely functional programming: removing side effects
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* FUNCTIONS */ | |
// Functions as objects | |
val suma2: (Int, Int) => Int = (x: Int, y: Int) => x + y | |
val suma1: Int => Int => Int = x => y => x+ y | |
val suma2: Function2[Int, Int, Int] = (x: Int, y: Int) => x + y | |
val suma1: Function1[Int, Function1[Int, Int]] = x => y => x+ y | |
suma2.curried : Function1[Int, Function1[Int, Int]] | |
suma2.tupled: Function1[(Int, Int), Int] | |
// Functions as methods | |
def suma2(x: Int, y: Int): Int = x + y | |
def suma1(x: Int)(y: Int): Int = x + y | |
suma2 _ : Function2[Int, Int, Int] | |
suma1 _ : Function1[Int, Function1[Int, Int]] | |
// Methods allow us to write generic functions | |
def f[T](t: T): T = t | |
// NOT POSSIBLE: val f: T ~> T => T = (t: T) => t | |
/* IMPURE FUNCTIONS */ | |
def sumaImpura(x: Int, y: Int): Int = { | |
val resultado = x + y | |
println("sumando ...") | |
resultado | |
} | |
/* PURIFYING FUNCTIONS */ | |
// First attempt | |
trait LoggingInstruction | |
case class Info(msg: String) extends LoggingInstruction | |
case class Warn(msg: String) extends LoggingInstruction | |
case class Debug(msg: String) extends LoggingInstruction | |
def sumaPuraConLogging(x: Int, y: Int): (LoggingInstruction, Int) = { | |
val resultado = x + y | |
(Info(s"$x + $y = $resultado"), resultado) | |
} | |
// Better with a type alias | |
type Logging[T] = (LoggingInstruction, T) | |
def sumaPuraConLogging2(x: Int, y: Int): Logging[Int] = | |
sumaPuraConLogging(x,y) | |
/* WHO DOES THE JOB */ | |
// the interpreter | |
def run(logging: Logging[Int]): Unit = logging match { | |
case (Info(msg), _) => | |
println("INFO: " + msg) | |
case (Warn(msg), _) => | |
println("WARN: " + msg) | |
case (Debug(msg), _) => | |
println(s"DEBUG: $msg") | |
} | |
// the main program | |
def main(): Unit = { | |
/* impuro */ | |
val x = readInt | |
val y = readInt | |
/* puro */ | |
val instrucciones = sumaPuraConLogging(x,y) | |
/* impuro */ | |
run(instrucciones) | |
} | |
// unit testing | |
assert( sumaPuraConLogging(3,4)._2 == 7 ) | |
assert( sumaPuraConLogging(3,4)._1 == Info("3 + 4 = 7") ) | |
// arbitrary interpreters reused business logic | |
def runWithLog4j(logging: Logging[Int]): Unit = ??? | |
def runWithSLF4J(logging: Logging[Int]): Unit = ??? | |
def runWithLogback(logging: Logging[Int]): Unit = ??? | |
/* COMPOSING PURE FUNCTIONS */ | |
// Composing impure functions | |
def negacionImpura(n: Int): Int = { | |
println("negando ...") | |
-n | |
} | |
def negacionPuraConLogging(n: Int): Logging[Int] = | |
(Warn(s"-$n"), -n) | |
negacionImpura( sumaImpura(3,4) ): Int | |
(negacionImpura _) compose (sumaImpura _).tupled | |
(sumaImpura _).tupled andThen negacionImpura | |
// Composing purely effectful functions | |
// NO COMPILA: negacionPuraConLogging( sumaPuraConLogging(3,4) ) | |
// NO COMPILA: (negacionPuraConLogging _) compose (sumaPuraConLogging _).tupled | |
// bind combinator | |
case class Multilog(logs: List[LoggingInstruction]) extends LoggingInstruction | |
case class Logging[T](log: LoggingInstruction, result: T){ | |
def bind[U](f: T => Logging[U]): Logging[U] = { | |
val Logging(log2, result2) = f(result) | |
Logging(Multilog(List(log, log2)), result2) | |
} | |
} | |
def negacionPuraConLogging(n: Int): Logging[Int] = | |
Logging(Warn(s"-$n"), -n) | |
def sumaPuraConLogging(x: Int, y: Int): Logging[Int] = { | |
val resultado = x + y | |
Logging(Info(s"$x + $y = $resultado"), resultado) | |
} | |
sumaPuraConLogging(3,4) bind negacionPuraConLogging : Logging[Int] | |
// pure & map combinators | |
case class Logging[T](log: LoggingInstruction, result: T){ | |
def bind[U](f: T => Logging[U]): Logging[U] = { | |
val Logging(log2, result2) = f(result) | |
Logging(Multilog(List(log, log2)), result2) | |
} | |
def map[U](f: T => U): Logging[U] = | |
Logging(log, f(result)) | |
} | |
def pure[T](t: T): Logging[T] = Logging(Multilog(List()), t) | |
def sumaPuraConLogging(x: Int, y: Int): Logging[Int] = { | |
val resultado = x + y | |
Logging(Info(s"$x + $y = $resultado"), resultado) | |
} | |
def negacionPura(n: Int): Int = -n | |
sumaPuraConLogging(3,4) bind ((negacionPura _) andThen pure) : Logging[Int] | |
sumaPuraConLogging(3,4) map negacionPura : Logging[Int] | |
/* ALL WITH SCALAZ: series/7.2.x */ | |
import scalaz._, Scalaz._, Free._ | |
trait LoggingInstruction[T] | |
case class Info(msg: String) extends LoggingInstruction[Unit] | |
case class Warn(msg: String) extends LoggingInstruction[Unit] | |
case class Debug(msg: String) extends LoggingInstruction[Unit] | |
def toId = new (LoggingInstruction ~> Id){ | |
def apply[A](fa: LoggingInstruction[A]): A = { | |
fa match { | |
case Info(msg) => println(s"INFO: $msg") | |
case Warn(msg) => println(s"WARN: $msg") | |
case Debug(msg) => println(s"DEBUG: $msg") | |
} | |
} | |
} | |
type Logging[T]=Free[LoggingInstruction,T] | |
implicit val MonadLogging: Monad[Logging] = Free.freeMonad[LoggingInstruction] | |
def info(msg: String): Logging[Unit] = Free.liftF(Info(msg)) | |
def warn(msg: String): Logging[Unit] = Free.liftF(Warn(msg)) | |
def debug(msg: String): Logging[Unit] = Free.liftF(Debug(msg)) | |
def returns[T](value: T): Logging[T] = Free.point[LoggingInstruction,T](value) | |
def runUnit[A](program: Logging[A]): A = | |
program.foldMap(toId) | |
def sumaPuraConLogging(x: Int, y: Int): Logging[Int] = { | |
val resultado = x + y | |
info(s"$x + $y = $resultado") map (_ => resultado) | |
} | |
def negacionPuraConLogging(n: Int): Logging[Int] = | |
info(s"-$n") as -n | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment