Saturday, April 29, 2017

Scala Dessign Patterns: Type Class Pattern

Introduction

An important principle of good code design is to avoid repetition and it is known as do not repeat yourself (DRY).

Ad Hoc Polymorphism: 

Ad Hoc Polymorphism is utilizing a possibly different implementations based on Types. 

Agenda

  • Implement Type Class Pattern using Scala.
  • Implement Type Class Pattern using Simulacrum.

Code: By Scala


trait Number[T] {

  def plus(t1: T, t2: T): T
  def minus(t1: T, t2: T): T
  def divide(t1: T, t2: Int): T
  def multiply(t1: T, t2: T): T
  def sqrt(t1: T): T
}

object Number {
  implicit object DoubleNumber extends Number[Double] {
    override def plus(t1: Double, t2: Double): Double = t1 + t2
    override def minus(t1: Double, t2: Double): Double = t1 - t2
    override def divide(t1: Double, t2: Int): Double = t1 / t2
    override def multiply(t1: Double, t2: Double): Double = t1 * t2
    override def sqrt(t1: Double): Double = Math.sqrt(t1)
  }

  implicit object IntNumber extends Number[Int] {
    override def plus(t1: Int, t2: Int): Int = t1 + t2 + 10
    override def minus(t1: Int, t2: Int): Int = t1 - t2
    override def divide(t1: Int, t2: Int): Int = t1 / t2
    override def multiply(t1: Int, t2: Int): Int = t1 * t2
    override def sqrt(t1: Int): Int = Math.sqrt(t1).toInt
  }
}


class StatsExample {

  def mean[T: Number] (xs: Vector[T]): T = implicitly[Number[T]]
    .divide (xs.reduce(implicitly[Number[T]].plus(_, _)), xs.size)

  //assume vector is in sorted order
  def median[T: Number] (xs: Vector[T]): T = xs(xs.size / 2)
}

object StatsExample extends App {

  val intVector = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 21, 22, 23, 24, 25)
  val doubleVector = Vector(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
    20.0, 21.0, 22.0, 23.0, 24.0, 25.0)

  val example = new StatsExample
  println(s"Mean (int) ${example.mean(intVector)}")
  println(s"Median (int) ${example.median(intVector)}")

  println(s"Mean (double) ${example.mean(doubleVector)}")
  println(s"Median (double) ${example.median(doubleVector)}")
}

Code: By Simulacrum


@typeclass trait Number[T] {

  @op("+") def plus(t1: T, t2: T): T
  @op("-") def minus(t1: T, t2: T): T
  @op("/") def divide(t1: T, t2: Int): T
  @op("*") def multiply(t1: T, t2: T): T
  @op("^") def sqrt(t1: T): T
}

object Number {
  implicit object DoubleNumber extends Number[Double] {
    override def plus(t1: Double, t2: Double): Double = t1 + t2
    override def minus(t1: Double, t2: Double): Double = t1 - t2
    override def divide(t1: Double, t2: Int): Double = t1 / t2
    override def multiply(t1: Double, t2: Double): Double = t1 * t2
    override def sqrt(t1: Double): Double = Math.sqrt(t1)
  }

  implicit object IntNumber extends Number[Int] {
    override def plus(t1: Int, t2: Int): Int = t1 + t2
    override def minus(t1: Int, t2: Int): Int = t1 - t2
    override def divide(t1: Int, t2: Int): Int = t1 / t2
    override def multiply(t1: Int, t2: Int): Int = t1 * t2
    override def sqrt(t1: Int): Int = Math.sqrt(t1).toInt
  }
}


class StatsExample {

  import Number.ops._

  def mean[T: Number] (xs: Vector[T]): T = xs.reduce(_ + _) / xs.size

  //assume vector is in sorted order
  def median[T: Number] (xs: Vector[T]): T = xs(xs.size / 2)
}

object StatsExample extends App {


  val intVector = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 21, 22, 23, 24, 25)
  val doubleVector = Vector(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
    20.0, 21.0, 22.0, 23.0, 24.0, 25.0)

  val example = new StatsExample
  println(s"Mean (int) ${example.mean(intVector)}")
  println(s"Median (int) ${example.median(intVector)}")

  println(s"Mean (double) ${example.mean(doubleVector)}")
  println(s"Median (double) ${example.median(doubleVector)}")
}

Download Code from Github Repo

 

References: