Imperative to functional

Transitioning the mindset

Artūras Šlajus

arturas@tinylabproductions.com

@arturaz_

Why FP?

  • to make programming easier
  • by reducing number of bugs
  • by making you use your brain less
  • by reusing code rather than reimplementing it

Referential transparency

// f(x, y) = x + y * x

scala> def f(x: Int, y: Int) = x + y * x
f: (x: Int, y: Int)Int

scala> f(5, 10)
res0: Int = 55

scala> f(3, 4)
res1: Int = 15

Equational reasoning

Substitution of function call by its returned value.

// f(5, 10) = 55; f(3, 4) = 15

scala> val program1 = f(5, 10) + f(3, 4)
program1: Int = 70

scala> val program2 = 55 + 15
program2: Int = 70

scala> val program3 = 70
program3: Int = 70

Now with side effects

We cannot substitute returned values and have the same program.

// scala> :t println (_: String)
// String => Unit

scala> val program1 = { 
  println("Hello"); println("World")
}
Hello
World
program1: Unit = ()

scala> val program2 = { (); () }
program2: Unit = ()

Context dependency

Code depends not only on arguments, but on position in code as well.

scala> var counter = 0
scala> def a = { counter += 1; counter * 2 }
scala> def b = { counter += 2; counter * 3 }

scala> counter = 0; a     // == 2
scala> counter = 0; b     // == 6
scala> counter = 0; 2 + 6 // == 8
scala> counter = 0; a + b // == 11 
scala> counter = 0; b + a // == 12

Imperative mindset

  • OOP or just data + procedures.
  • Mutable variables and data structures.
  • Relies on side effects to do things. Return type is usually void.

Purely functional mindset

  • Data + pure functions.
  • Immutable variables and data structures.
  • Functions do not perform side effects and are fully defined through their type signature.

Simple Counter

Functional Computations

Mutable

class Counter(private[this] var _count: Int) {
  def count = _count
  def inc(): Unit = _count += 1
  def dec(): Unit = _count -= 1
}

Mutable usage

object CounterApp extends App {
  def makeCount(c: Counter): Vector[Int] = {
    val count0  = counter.count
    val count1  = { counter.inc(); counter.count }
    val count2  = { counter.inc(); counter.count }
    val count3  = { counter.dec(); counter.count }
    Vector(count0, count1, count2, count3)
  }

  val counter = new Counter(0)
  val counts = makeCount(counter)
  println(s"counter=$counter, counts=$counts")
}

Immutable

case class Counter(count: Int) {
  def inc: Counter = copy(count = count + 1)
  def dec: Counter = copy(count = count - 1)
}

Immutable usage

object CounterApp extends App {
  def makeCount(c: Counter): (Counter, Vector[Int]) = {
    val c1 = c.inc
    val c2 = c1.inc
    val c3 = c2.dec
    (c3, Vector(c.count, c1.count, c2.count, c3.count))
  }

  val (currentCounter, counts) = makeCount(Counter(0))
  println(s"counter=$currentCounter, counts=$counts")
}

FizzBuzz

Functional IO

object FizzBuzz extends App { /* Imperative */
  var line = ""
  def read() = {
    print("Enter a number (done to finish): ")
    line = StdIn.readLine().stripLineEnd
  }

  read()
  while (line != "done") {
    try {
      val int = line.toInt
      if (int % 3 == 0 && int % 5 == 0) println("fizzbuzz")
      else if (int % 3 == 0) println("fizz")
      else if (int % 5 == 0) println("buzz")
    }
    catch {
      case _: NumberFormatException => 
        println(s"$line was not a number!")
    }

    read()
  }
}

Functional

  class Later[A](private val f: () => A) extends AnyVal {
    /* Returns a new Later which will combine this 
       and l2 when evaluated. */
    def flatMap[B](l2: A => Later[B]): Later[B] =
      Later { l2(f()).endOfTheUniverse_!() }

    /* Just evaluates f. Call at the end of the program. */
    def endOfTheUniverse_!(): A = f()
  }
  object Later {
    def apply[A](f: => A): Later[A] = new Later(() => f)
  }

Some helpful machinery

  def int2answer(i: Int): Option[String] =
    if (i % 3 == 0 && i % 5 == 0) Some("fizzbuzz")
    else if (i % 3 == 0) Some("fizz")
    else if (i % 5 == 0) Some("buzz")
    else None

Pure computation

  val read: Later[String] = Later {
    print("Enter a number (done to finish): ")
    StdIn.readLine().stripLineEnd
  }

  def line2print(line: String): Later[Unit] = Later {
    try int2answer(line.toInt).foreach(println)
    catch {
      case _: NumberFormatException => 
        println(s"$line was not a number!")
    }
  }

Pure IO

object FizzBuzz extends App { /* Functional */
  /* ... previous code ... */

  lazy val main: Later[Unit] = read.flatMap {
    case "done" => Later(())
    case line => line2print(line).flatMap(_ => main)
  }

  main.endOfTheUniverse_!()
}

Main application

Real Life Example

Simple Game Model

  • There is a world composed of world objects.
  • Asteroids have some resources in them.
  • Extractors are built on asteroids and extract resources from them for the player.
  • World knows how much resources we've extracted so far and how much resources are left.
  • Objects can be added or removed from the world.

Mutable

ID counting

object IdCounter {
  type Id = Int
}
class IdCounter {
  private[this] var nextId = 0
  def newId() = {
    val id = nextId
    nextId += 1
    id
  }
}

World Object trait

trait WObject {
  val id: IdCounter.Id
}

Asteroid class

class Asteroid(
  val id: IdCounter.Id, var resources: Int
) extends WObject

Extractor class

class Extractor(
  val id: IdCounter.Id, 
  asteroid: Asteroid, extractionRate: Int
) extends WObject {
  def extract(): Int = {
    val resources = 
      extractionRate min asteroid.resources
    asteroid.resources -= resources
    resources
  }
}

World class

class World {
  val objects = mutable.Set.empty[WObject]

  def extracted = _extracted
  private[this] var _extracted = 0

  def resourcesLeft = objects.collect { 
    case a: Asteroid => a.resources
  }.sum

  /* ... */

World class

  /* ... */

  def turnStart(): Int = {
    val extracted = objects.collect { 
      case e: Extractor => e.extract() 
     }.sum
    _extracted += extracted
    extracted
  }
}

Resource extraction

  def extract(world: World): Unit = {
    println(s"Resources Left: ${world.resourcesLeft}")
    println(
      s"Extracted, before: ${world.extracted
      }, this turn: ${world.turnStart()
      }, after: ${world.extracted}"
    )
    println(s"Resources Left after extraction: ${
      world.resourcesLeft}")
    println()
  }
  def main(args: Array[String]): Unit = {
    val ids = new IdCounter
    val asteroid = new Asteroid(ids.newId(), 10)
    val extractor = 
      new Extractor(ids.newId(), asteroid, 4)

    val world = new World
    world.objects.add(asteroid)
    world.objects.add(extractor)

    while (world.resourcesLeft > 4) extract(world)
    println("!!! Removing asteroid !!!")
    world.objects.remove(asteroid)
    extract(world)
  }

There's a bug in the code

Can you spot it?

[info] Running game.mutable.MutableReferences 
Resources Left: 10
Extracted, before: 0, this turn: 4, after: 4
Resources Left after extraction: 6

Resources Left: 6
Extracted, before: 4, this turn: 4, after: 8
Resources Left after extraction: 2

!!! Removing asteroid !!!
Resources Left: 0
Extracted, before: 8, this turn: 2, after: 10
Resources Left after extraction: 0

Functional

ID counting

case class IdCounter(id: IdCounter.Id=0) {
  def next = IdCounter(id + 1)
}
object IdCounter {
  type Id = Int
}

World Object trait

sealed trait WObject {
  val id: IdCounter.Id
}

Asteroid class

case class Asteroid(
  id: IdCounter.Id, resources: Int
) extends WObject {
  def +(res: Int) = copy(resources = resources + res)
  def -(res: Int) = this + -res
}
case class Extractor(
  id: IdCounter.Id, asteroidId: IdCounter.Id,
  extractionRate: Int
) extends WObject {
  def extract(world: World): (World, Int) = {
    world.find[Asteroid](asteroidId).fold(
      (world, 0)
    ) { asteroid =>
      val resources = 
        extractionRate min asteroid.resources
      (
        world.updated(asteroid - resources),
        resources
      )
    }
  }
}
object World {
  def apply(objects: TraversableOnce[WObject]): World =
    objects.foldLeft(World())(_.updated(_))
}
case class World(
  objectMap: Map[IdCounter.Id, WObject]=Map.empty,
  extracted: Int = 0
) {
  def find[A <: WObject : ClassTag](id: IdCounter.Id) =
    objectMap.get(id).collect { case a: A => a }

  def updated(obj: WObject) = 
    copy(objectMap = objectMap + (obj.id -> obj))
  def remove(id: Id): World = 
    copy(objectMap = objectMap - id)

  /* ... */
  /* ... */

  def resourcesLeft = objects.collect { 
    case a: Asteroid => a.resources 
  }.sum

  def objects = objectMap.values

  /* ... */
  /* ... */

  def turnStart: (World, Int) = {
    val (newWorld, totalExtracted) = objects
      .collect { case e: Extractor => e }
      .foldLeft((this, 0)) { 
        case ((world, totalExtracted), e) =>
          val (newWorld, extracted) = e.extract(world)
          (newWorld, totalExtracted + extracted)
      }
    (
      newWorld.copy(
        extracted = newWorld.extracted + totalExtracted
      ),
      totalExtracted
    )
  }
}
  def extract(world: World): World = {
    val (newWorld, extracted) = world.turnStart

    println(s"Resources Left: ${world.resourcesLeft}")
    println(
      s"Extracted, before: ${world.extracted
      }, this turn: $extracted, after: ${
      newWorld.extracted}"
    )
    println(s"Resources Left after extraction: ${
      newWorld.resourcesLeft}")
    println()
    newWorld
  }
  def main(args: Array[String]): Unit = {
    val ids = IdCounter()
    val asteroid = Asteroid(ids.id, 10)
    val extractorIds = ids.next
    val extractor = 
      Extractor(extractorIds.id, asteroid.id, 4)

    var world = World(Seq(asteroid, extractor))

    while (world.resourcesLeft > 4) world = extract(world)
    println("!!! Removing asteroid !!!")
    world = world.remove(asteroid.id)
    extract(world)
  }
[info] Running game.immutable.FPGame 
Resources Left: 10
Extracted, before: 0, this turn: 4, after: 4
Resources Left after extraction: 6

Resources Left: 6
Extracted, before: 4, this turn: 4, after: 8
Resources Left after extraction: 2

!!! Removing asteroid !!!
Resources Left: 0
Extracted, before: 8, this turn: 0, after: 8
Resources Left after extraction: 0

Scalaz versions

Counter

case class Counter(count: Int)

object Counter {
  def doAndGet(f: Counter => Counter) = 
    // State[S, A] is an abstraction over S => (S, A)
    State { (c: Counter) =>
      val nc = f(c)
      (nc, nc.count)
    }
  val count = State.gets((_: Counter).count)
  val inc = doAndGet(c => c.copy(count = c.count + 1))
  val dec = doAndGet(c => c.copy(count = c.count - 1))
}

Definition

Usage

object CounterApp extends App {
  val makeCount = Vector(
    Counter.count, Counter.inc, Counter.inc, 
    Counter.dec
  ).sequenceU
  val (counter, counts) = makeCount(Counter(0))
  println(s"counter=$counter, counts=$counts")
}

FizzBuzz

object FizzBuzz extends SafeApp { /* Scalaz */
  def int2answer(i: Int): Option[String] =
    if (i % 3 == 0 && i % 5 == 0) Some("fizzbuzz")
    else if (i % 3 == 0) Some("fizz")
    else if (i % 5 == 0) Some("buzz")
    else None

  val read: IO[String] = for {
    _ <- IO.putStr("Enter a number (done to finish): ")
    line <- IO.readLn.map(_.stripLineEnd)
  } yield line

  def line2print(line: String): IO[Unit] = parseInt(line).fold(
    _ => IO.putStrLn(s"$line was not a number!"),
    int2answer(_).fold(IO.ioUnit)(IO.putStrLn)
  )

  override def runc: IO[Unit] = {
    lazy val main: IO[Unit] = for {
      line <- read
      _ <- if (line == "done") IO.ioUnit
           else line2print(line) flatMap (_ => main)
    } yield ()

    main
  }
}

Game

Thank you

Artūras Šlajus

arturas@tinylabproductions.com

@arturaz_

Imperative to FP to Scalaz - transitioning the mindset

By Artūras Šlajus

Imperative to FP to Scalaz - transitioning the mindset

We'll be looking at several simple programs and their implementations in imperative, purely functional and purely functional done with scalaz mindsets.

  • 1,064
Loading comments...

More from Artūras Šlajus