Adrian
Adrian

Reputation: 5681

down casting for type classes in Scala

I have

trait OptionTransaction {
  def data: Data
}

BuyOptionTransaction extends OptionTransaction
SellOptionTransaction extends OptionTransaction

I use these with a Formatter type class to create string representations of various transactions

trait Formatter[T] {
  def format(ot:T):String
}

object Formatter {
  def apply[T](implicit screen: Formatter[T]) = screen

  implicit val buyOT = new Formatter[BuyOptionTransaction] {
    def format(ot: BuyOptionTransaction):String = ot.x.toString
  }

  implicit val sellOT = new Formatter[SellOptionTransaction] {
    def format(ot: SellOptionTransaction):String = ot.y.toString
  }
}

This is the entry point:

import Formatter._
val closeTransactions: List[OptionTransaction] = ...
closeTransactions.map(startFormat)

The problem is that closeTransactions has type List[OptionTransaction] and the type class needs OptionTransaction downcast to BuyOptionTransaction or SellOptionTransaction otherwise it won't find the implicit formatter.

How can I achieve this downcast automatically?

Upvotes: 1

Views: 1865

Answers (2)

Suma
Suma

Reputation: 34393

If you want to deal with the polymorphism runtime, you need to implement some kind of dynamic (runtime) dispatch instead of Type classes, which are static one (compile time). It could look like this:

type Data = String
trait OptionTransaction {
  def data: Data = ""
}

class BuyOptionTransaction extends OptionTransaction {
  def x: String = "X"
}
class SellOptionTransaction extends OptionTransaction {
  def y: String = "Y"

}

trait Formatter[T] {
  def format(ot:T):String
}

object Formatter {
  def apply[T](implicit screen: Formatter[T]) = screen

  def selectFormatter[T](obj: T)(implicit formatter: Formatter[T]) = formatter

  implicit val buyOT = new Formatter[BuyOptionTransaction] {
    def format(ot: BuyOptionTransaction):String = ot.x.toString
  }

  implicit val sellOT = new Formatter[SellOptionTransaction] {
    def format(ot: SellOptionTransaction):String = ot.y.toString
  }

  implicit val ot = new Formatter[OptionTransaction] {
    def format(ot: OptionTransaction):String = ot match {
      case ot: BuyOptionTransaction =>
        selectFormatter(ot).format(ot)
      case ot: SellOptionTransaction =>
        selectFormatter(ot).format(ot)
    }
  }
}

def startFormat[T](ot: T)(implicit ev: Formatter[T]) = {
  ev.format(ot)
}
import Formatter._

val closeTransactions: List[OptionTransaction] = List(new BuyOptionTransaction, new SellOptionTransaction)

closeTransactions.map(startFormat(_))

Upvotes: 2

Gábor Bakos
Gábor Bakos

Reputation: 9100

You can collect the appropriate types:

val closeTransactions: List[OptionTransaction] = ???
val buys = closeTransactions.collect { case b: BuyOptionTransaction => b}
val sells = closeTransactions.collect { case s: SellOptionTransaction => s}

Now you can apply the proper typeclasses.

It would be probably better to add the action/transformation you want to the OptionTransaction trait and use that for dynamic binding. If you want to keep working only for one of them, please take a look at this answer.

Upvotes: 1

Related Questions