Search code examples
scalaimplicitmonkeypatchingabstract-data-type

Using implicit methods inside case classes ADT


I am attempting to use implicits to try and override a serialize method that I have inside case classes for an ADT, however I am unable to get my head around how it should be done

// START OF API//

/**
 * Serialize methods defined here
 *
 */

object Serializer {
  def string(block: (String => String)) = block
  def int(block:(Int => String)) = block
  def double(block:(Double => String)) = block
}

/**
 * These are the DEFAULT serializers
 */

object Implicits {
  implicit val string = Serializer.string (s => s)
  implicit val int = Serializer.int (i => i.toString)
  implicit val double = Serializer.double (d => String.valueOf(d))
}

/**
 *
 * Our simple ADT defined here
 */

import Implicits._

abstract class Vehicle {
  def serialize:String
}

case class Car(s:String) extends Vehicle {
  def serialize: String = string(s)
}

case class Truck(i:Int) extends Vehicle {
  def serialize: String = int(i)
}

case class RocketShip(d:Double) extends Vehicle {
  def serialize: String = double(d)
}

// END OF API

// START OF USER CODE

object UserImplicit {
  implicit val string = Serializer.string(s => s.capitalize)
}

object Main extends App{
  val v = Car("some car")

  println(v.serialize)
  import test.UserImplicit._
  // This SHOULD print a capatilized version i.e. SOME CAR
  println(v.serialize)
}

// END OF USER CODE

Basically I want to monkey patch the default serializer methods (the ones contained inside object Implicits) so that users of the API can implement their own serializers

I have tried many combinations (such as having the implicits inside a trait instead of a object) however I havn't actually managed to get it working


Solution

  • I actually managed to figure it out using the pimp my library pattern, here is an example of the working code

    object Implicits {
      def defaults(v:Vehicle):String = {
        v match {
          case Car(c) => "This is car " + c
          case Truck(t) => "This is a truct " + t
          case RocketShip(r) => "This is a rocketship " + r
        }
      }
    
      class VehicleSerializer(v:Vehicle) {
        def serialize:String = defaults(v)
      }
    
      implicit def vSerialize(v:Vehicle) = new VehicleSerializer(v)
    }
    
    /**
     * Our simple ADT defined here
     */
    abstract class Vehicle {}
    case class Car(s: String) extends Vehicle {}
    case class Truck(i: Int) extends Vehicle {}
    case class RocketShip(d: Double) extends Vehicle {}
    
    // END OF API
    
    // START OF USER CODE
    
    class UserImplicit(v:Vehicle) {
      def serialize:String = {
        v match {
          case Car(c) => "This is a MASSIVE car " + c
          case _v => Implicits.defaults(_v)
        }
    
      }
    }
    
    object Test extends App {
      val c = Car("rawr")
    
      // This is the default serializer
      {
        import Implicits._
        println(c.serialize)
      }
    
      // This is our overwritten serializer
      {
        implicit def vSerialize(v:Vehicle) = new UserImplicit(v)
        println(c.serialize)
      }
    }
    
    // END OF USER CODE
    

    which prints

    This is car rawr
    This is a MASSIVE car rawr
    

    As intended