Upgrade to Pro — share decks privately, control downloads, hide ads and more …

OOP vs FP: The Pursuit of Extensibility KSUG 04/18

OOP vs FP: The Pursuit of Extensibility KSUG 04/18

Slides form Kraków Scala User Group meetup

Adam Gajek

April 05, 2018
Tweet

Other Decks in Programming

Transcript

  1. OOP VS FP? THE PURSUIT OF EXTENSIBILITY ADAM GAJEK https://twitter.com/adam_gajek

    https://medium.com/@adamgajek https://github.com/agajek
  2. trait Expr { def eval: Double } case class Number(value:

    Double) extends Expr { override def eval = value } trait Expr case class Number(value: Double) extends Expr FP OOP INITIAL
  3. trait Expr { def eval: Double } case class Number(value:

    Double) extends Expr { override def eval = value } case class Add(a: Expr, b: Expr) extends Expr { override def eval = ??? } trait Expr case class Number(value: Double) extends Expr case class Add(a: Expr, b: Expr) extends Expr FP OOP INITIAL def evaluate(e: Expr): Double = e match { case Number(a) => a case Add(a, b) => ??? }
  4. trait Expr { def eval: Double } case class Number(value:

    Double) extends Expr { override def eval = value } case class Add(a: Expr, b: Expr) extends Expr { override def eval = a.eval + b.eval } trait Expr case class Number(value: Double) extends Expr case class Add(a: Expr, b: Expr) extends Expr def evaluate(e: Expr): Double = e match { case Number(a) => a case Add(a, b) => evaluate(a) + evaluate(b) } FP OOP INITIAL
  5. def print(e: Expr): String = e match { case Number(a)

    => a.toString case Add(a, b) => s”(${print(a)} + ${print(b)})" } trait Expr { def eval: Double def print: String } case class Number(value: Double) extends Expr { override def eval = value override def print = value.toString } case class Add(a: Expr, b: Expr) extends Expr { override def eval = a.eval + b.eval override def print = s"(${a.print} + ${b.print})" } FP OOP OPERATION
  6. case class Neg(a: Expr) extends Expr def evaluate(e: Expr): Double

    = e match { case Number(a) => a case Add(a, b) => evaluate(a) + evaluate(b) case Neg(a) => - evaluate(a) } def print(e: Expr): String = e match { case Number(a) => a.toString case Add(a, b) => s"(${print(a)} + ${print(b)})" case Neg(a) => s"-${print(a)}" } case class Neg(a: Expr) extends Expr { override def eval = - a.eval override def print = s"-${a.print}" } FP OOP FORM
  7. FP EXPRESSION PROBLEM SUM UP - OOP HARD IN EXTENDING

    FUNCTIONALITY - FP HARD IN EXTENDING IMPLEMENTATIONS
  8. TYPE CLASSES Wikipedia The Power of Ad-hoc Polymorphism "In computer

    science, a type class is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types. Such a constraint typically involves a type class T and a type variable A, and means that [A] can only be instantiated to a type whose members support the overloaded operations associated with [T].”
  9. trait Eq[A] { def areEquals(a: A, b: A): Boolean }

    class Eq a where areEquals :: a -> a -> Boolean SCALA HASKELL
  10. class Eq a where areEquals :: a -> a ->

    Boolean SCALA HASKELL trait Eq[A] { def areEquals(a: A, b: A): Boolean } implicit val moduloEq: Eq[Int] = new Eq[Int] { def areEquals(a: Int, b: Int) = a % 5 == b % 5 } instance Eq Int where areEquals a b = a % 5 == b % 5
  11. pairEquals :: (Eq a) => a -> a -> Maybe

    (a, a) pairEquals a b = if a `areEquals` b then Just (a, b) else Nothing SCALA HASKELL def pairEquals[A](a: A, b: A)(implicit eq: Eq[A]) = { if(eq.areEquals(a, b)) Some((a, b)) else None } trait Eq[A] { def areEquals(a: A, b: A): Boolean } implicit val moduloEq: Eq[Int] = new Eq[Int] { def areEquals(a: Int, b: Int) = a % 5 == b % 5 } instance Eq Int where areEquals a b = a % 5 == b % 5 class Eq a where areEquals :: a -> a -> Boolean
  12. CONTEXT BOUNDS def pairEquals[A: Eq](a: A, b: A): Option[(A, A)]

    = { if(???.areEquals(a, b)) Some((a, b)) else None }
  13. CONTEXT BOUNDS def pairEquals[A: Eq](a: A, b: A): Option[(A, A)]

    = { if(implicitly[A].areEquals(a, b)) Some((a, b)) else None }
  14. CONTEXT BOUNDS object Eq { def apply[A](implicit eq: Eq[A]): Eq[A]

    = eq } def pairEquals[A: Eq](a: A, b: A): Option[(A, A)] = { if(Eq[A].areEquals(a, b)) Some((a, b)) else None }
  15. TYPE ENRICHMENT def pairEquals[A: Eq](a: A, b: A): Option[(A, A)]

    = { if(a === b) Some((a, b)) else None } object Eq { def apply[A](implicit eq: Eq[A]): Eq[A] = eq } implicit class EqSyntax[A: Eq](a: A) { def ===(b: A): Boolean = Eq[A].areEquals(a, b) }
  16. import simulacrum._ @typeclass trait Eq[A] { @op(“===”) def areEquals(a: A,

    b: A): Boolean } SIMULACRUM def pairEquals[A: Eq](a: A, b: A): Option[(A, A)] = { if(a === b) Some((a, b)) else None } HASKELL pairEquals :: (Eq a) => a -> a -> Maybe (a, a) pairEquals a b = if a `areEquals` b then Just (a, b) else Nothing instance Eq Int where areEquals a b = a % 5 == b % 5 class Eq a where areEquals :: a -> a -> Boolean
  17. TYPE CLASSES trait Expression case class Number(a: Double) extends Expression

    case class Add[A <: Expression, B <: Expression](a: A, b: B) extends Expression 
 @typeclass trait Eval[A <: Expression] { def eval(expression: A): Double }
  18. INITIAL object Eval { def evaluate[A <: Expression : Eval](expression:

    A) = Eval[A].eval(expression) implicit val numberExpr: Eval[Number] = (expression: Number) => expression.a } object Add { import Eval.evaluate
 implicit def addExpr[A <: Expression : Eval, B <: Expression : Eval]: Eval[Add[A, B]] = (expr: Add[A, B]) => evaluate(expr.a) + evaluate(expr.b) }
  19. NEW FORM case class Mul[A <: Expression, B <: Expression](a:

    A, b: B) extends Expression
 object Mul { import Eval.evaluate
 implicit def mulExpr[A <: Expression : Eval, B <: Expression : Eval]: Eval[Mul[A, B]] = (expr: Mul[A, B]) => evaluate(expr.a) * evaluate(expr.b) } Eval.evaluate(Mul(Add(Number(2), Number(2)), Number(2)))
  20. NEW OPERATION @typeclass trait Show[A <: Expression] { def print(expr:

    A) : String } object Show { def print[A <: Expression](expr: A)(implicit show: Show[A]) = show.print(expr) implicit def addPrint[A <: Expression : Show, B <: Expression : Show]: Show[Add[A, B]] = { (expr: Add[A, B]) => print(expr.a) + " + " + print(expr.b) } }