Being Purely Functional
in an Impure World
Noel Markham (47 Degrees)

Noel Markham (47 Degrees)
Started life as a Java developer
Writing Scala since 2012:
What does this function do?
def reverse(s: List[String]): List[String]
scala> reverse(List("a", "b", "c"))
res0: List[String] = List(c, b, a)
scala> reverse(List("Test", "Input"))
res1: List[String] = List(Hello, World)
???
What about this function?
def identity(i: Int): Int
scala> identity(0)
res2: Int = 0
scala> identity(-6)
res3: Int = 0
Can we do better?
Revisiting our identity function
def identity[A](a: A): A = a
scala> identity(0)
res4: Int = 0
scala> identity(-6)
res5: Int = -6
How many ways can this be implemented?
Revisiting our reverse function
def reverse[A](as: List[A]): List[A] = {
as match {
case Nil => Nil
case head :: tail => reverse(tail) :+ head
}
}
scala> reverse(List("Test", "Input"))
res6: List[String] = List(Input, Test)
How many ways can this be implemented?
Counterintuitive
Simply by adding a constraint we've set our code free
More general
One implementation regardless of type
We know nothing about the input
We cannot change or manipulate it in any way
Types are documentation!
We can infer some behaviour simply from type signatures
Using type parameters is better than unit testing
If there's only one way to write a particular function, there's often very few ways to test it
final
readonly
Immutability is switched off by default
val
as opposed to var
ref
for mutationsImmutability is switched on by default
A small hurdle for the programmer to turn it on
val xs = List(0, 1, 2)
val ys = List(3, 4, 5)
By VineetKumar at English Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=3594533
val zs = xs ++ ys
By VineetKumar at English Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=3594592
By VineetKumar at English Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=3594620
From scala.collection.immutable.List
:
override def drop(n: Int): List[A] = {
var these = this
var count = n
while (!these.isEmpty && count > 0) {
these = these.tail
count -= 1
}
these
}
Mutable code! In the standard library too!
What gives?
Premature optimization is the root of all evil
It is very easy to step outside of the boundaries of immutability
Lean on your compiler and other tools as much as possible
Simply put, calling a function multiple times with the same inputs will always result in the same output
The compiler can defer function execution to a time of its choosing
The compiler can replace calls with the same arguments as the result of a single call
This is not unique to functional programming
Some functional languages enforce referential transparency, most do not
In impure languages, you need to think of referential transparency as a mindset
What are side effects?
A non-exhaustive list:
Examples:
Future
in ScalaIO
in HaskellNote, Scala's Future
is very different to Java's Future
Again, it is very easy to step outside of the boundaries of immutability
Establish conventions within your team
Lean on your compiler if you can
This is also applicable to languages like Scala
Nothing to do with OO classes
Essentially the way to add an interface to a class after that class has already been defined
This means you can provide different "interface-style" functionality depending on your needs
For instance, you could provide your own response to "to String" functionality for a particular type
Specify properties about your code, let the tests provide the data
import org.scalacheck.Properties
import org.scalacheck.Prop.forAll
object ReverseProperties extends Properties("Test Cases") {
property("reverse") = forAll { (s: String) =>
s.reverse.reverse == s
}
}
> test
[info] + Test Cases.reverse: OK, passed 100 tests.
object ReverseProperties extends Properties("Test Cases") {
property("absolute") = forAll { (i: Int) =>
Math.abs(i) >= 0
}
}
> test
[info] ! Test Cases.absolute: Falsified after 8 passed tests.
[info] > ARG_0: -2147483648
We've covered quite a bit of ground
Parametric polymorphism:
Immutability:
Referential Transparency:
When trying to be purely functional in an impure language, you must be vigilant
Corner-cutting techniques are everywhere and can be very tempting
It's up to YOU to transmit a mentality of doing things the right way, and why you should do things that way