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.
- 2,727