Skip to content

Instantly share code, notes, and snippets.

@jserranohidalgo
Last active August 29, 2015 14:23
Show Gist options
  • Save jserranohidalgo/31e7ea8efecfb36276cf to your computer and use it in GitHub Desktop.
Save jserranohidalgo/31e7ea8efecfb36276cf to your computer and use it in GitHub Desktop.
The essence of purely functional programming: removing side effects
/* 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