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

Avatar for Adam Gajek

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) } }