Agenda
• Thinking less with Functions
• Thinking less with Data
• Thinking less with Patterns
• Summary
• Questions
Function Signatures
Visualizing
def f(i: Int): String = ???
Types and values
def f(i: Int): String = ???
Function
def f(i: Int): String = ???
Visualizing the function signature
def divide(a: Int, b: Int): Int = {
if(b == 0)
throw new ArithmeticException("/ by zero")
a / b
}
Feed it 0
divide(2, 0)
Bad function!
scala> divide(2, 0)
java.lang.ArithmeticException: / by zero
at .divide(<console>:14)
... 54 elided
Bad function
signature!
def divide(a: Int, b: Int): Int = {
if(b == 0)
throw new ArithmeticException("/ by zero")
a / b
}
Total Function
def inc(x: Int): Int = x+1
Pure Function
def inc(x: Int): Int = {
println("a")
x+1
}
Convert divide to a total function
case class DivisionError(e: String)
def divide(a: Int, b: Int): Either[DivisionError, Int] = {
if( b == 0) Left(DivisionError("/ by zero"))
Right(a / b)
}
Container[_] types
Option[_]: Returning no value is
possible
def getMiddleName(name: String):
Option[String] = ???
Either[_,_]: Possibility of errors
def divide(a: Int, b: Int):
Either[String, Int] = ???
List[_]: Multiple outputs
def getHosts(mongoUri: String):
List[String] = ???
Future[_]: When?
def loadUser(id: String):
Future[String] = ???
Recap: Function signatures
• Total functions have a corresponding
output value to each input value
• Pure functions have no side effects
• Enrich your signature with Container
types
Good function signatures:
Think less
About implementation
Parametric
Functions
Let's play a game!
How many implementations does
this function have?
def foo[A](a: A): A = ???
How many implementations does
this function have?
def foo[A](a: A): A = a
How many implementations does
this function have?
def foo[A](as: List[A]): List[A] = ???
How many implementation does
this function have?
def foo[A](as: List[A]): List[A] = {
if(as.isEmpty) Nil
else as.head :: Nil
}
def foo[A](as: List[A]): List[A] = {
if(as.isEmpty) Nil
else as.tail.toList.head :: Nil
}
How many implementation does
this function have?
def foo[A](a: A): Int = ???
How many implementation does
this function have?
def foo[A](a: A): Int = 1
def foo[A](a: A): Int = 2
def foo[A](a: A): Int = 3
def foo[A](a: A): Int = 4
Recap: parametric
functions
• Increase abstraction,
Lower implementation
space
• This notion is called
parametricity
Parametricity:
Think less
About your implementation
Beyond Functions
What about real design?
• Design data
• Design behavior
Encoding data using
Sum Types
Encoding data using Sum Types
sealed trait Developer
case class DevOps(name: String) extends Developer
case class FullStack(name: String, salary: Int) extends Developer
case class ML(name: String, salary: Long) extends Developer
Exhaustiveness Checking on ADTs
scala> def sayHi(d: Developer) = d match {
|
| case DevOps(n) => s"Hi $n !"
|
| case FullStack(n, _) => s"Hi $n !"
|
| }
<console>:17: warning: match may not be exhaustive.
It would fail on the following input: ML(_, _)
def sayHi(d: Developer) = d match {
^
sayHi: (d: Developer)String
String email or id?
case class User(
id: String,
email: String,
created: Long
)
def loadUser(user: String): User = ???
Encode your domain with Value
classes
case class Id(id: String) extends AnyVal
case class Email(email: String) extends AnyVal
case class TimeStamp(ts: Long) extends AnyVal
case class User(id: Id, email: Email, created: TimeStamp)
def loadUser(user: Email): User = ???
Recap: ADTs / Value classes
• ADTs encode immutable data
• Value classes let you add domain
knowledge
ADTs/Value classes:
Think less
About Correctness
Patterns
Fit this in your mental
stack!
Patterns at the function level
1 + 1
"a".concat("b")
List(1,2,3) ++ List(4,5,6)
Refactoring to a common shape
def combineInts(x: Int, y: Int): Int = ???
def combineString(x: String, y: String): String = ???
def combineLists[A](x: List[A], y: List[A]): List[A] = ???
Extracting to typeclasses
trait Combinable[A] {
def combine(x: A, y: A): A
}
You can implement
instances
Combinable[Int]
implicit val combineInts =
new Combinable[Int] {
def combine(x: Int, y: Int): Int =
x + y
}
Combinable[String]
implicit val combineStrings =
new Combinable[String] {
def combine(x: String, y: String): String =
x + y
}
Combinable[Foo]
case class Foo(i: Int, s: String)
implicit val combineFoos =
new Combinable[Foo] {
def combine(x: Foo, y: Foo): Foo =
Foo(x.i + y.i , x.s.concat(y.s))
}
Use the instances
scala> implicitly[Combinable[String]].combine("a", "b")
res6: String = ab
scala> implicitly[Combinable[Int]].combine(1, 1)
res7: Int = 2
scala> implicitly[Combinable[Foo]].combine(Foo(1,"a"), Foo(2,"b"))
res8: Foo = Foo(3,ab)
Nice Refactoring!
Did we gain
anything?
Combinable[A]
trait Combinable[A] {
def combine(x: A, y: A): A
}
Monoid[A]
trait Monoid[A] {
def empty: A
def combine(x: A, y: A): A
}
Or you can just import
Monoid
And get free
instances!
import cats.Monoid
import cats.instances.all._
import cats.syntax.monoid._
scala> 1.combine(1)
res9: Int = 2
scala> "a".combine("b")
res10: String = ab
scala> List(1,2,3).combine(List(4,5,6))
res11: List[Int] = List(1, 2, 3, 4, 5, 6)
"Everything sufficiently
polymorphic and useful
already exists"
-- Rob Norris @tpolecat
More Free code!
import cats.derived.MkMonoid._
scala> Foo(1,"a").combine(Foo(2,"b"))
res12: Foo = Foo(3,ab)
combineAll is a derived function
trait Monoid[A] {
def empty: A
def combine(x: A, y: A): A
def combineAll(as: List[A]): A =
as.foldLeft(empty)(combine)
}
scala> Monoid[Int].combineAll(List(1,2,3))
res13: Int = 6
scala> Monoid[String].combineAll(List("a","b","c"))
res14: String = abc
scala> Monoid[Foo].combineAll(Set(Foo(1,"a"),Foo(2,"b"),Foo(3,"c")))
res15: Foo = Foo(6,abc)
Recap: Functional Patterns
• Functional patterns are encoded as typecalsses
• As opposed to Design patterns, functional
patterns are encoded as libraries like cats
• They come with free implementations
• Ecosystem of libraries working with these
patterns
One very important thing
• As opposed to frameworks, functional
patterns knowledge does not get old
• You can carry with you through your career,
regardless of languages or platforms
• Some languages even support them out of
the box λ
Summary
• Function Signatures with total/pure
functions and container types
• Data encoded with sealed trait/case
classes
• Patterns at the function level with
typeclasses using parametric functions
Questions
Thank You
@dsebban
Come chat about FP @ BigPanda
booth
Scalapeno18 - Thinking Less with Scala

Scalapeno18 - Thinking Less with Scala