1

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?

Suma
  • 33,181
  • 16
  • 123
  • 191
Adrian
  • 5,603
  • 8
  • 53
  • 85
  • Could you change definitions of `OptionTransaction` and its derived classes? Normally one would just add abstract `format` to `OptionTransaction` and implemented it as needed in derived classes, getting very simple and efficient solution. – Suma Dec 02 '17 at 19:31
  • If was familiar to me ... I think I have already answered a question similar enough so that this can be be marked as a duplicate: [Get Runtime Type picked by implicit evidence](https://stackoverflow.com/questions/42292338/get-runtime-type-picked-by-implicit-evidence/42293934#42293934) – Suma Dec 02 '17 at 19:36

2 Answers2

2

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(_))
Suma
  • 33,181
  • 16
  • 123
  • 191
1

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.

Gábor Bakos
  • 8,982
  • 52
  • 35
  • 52
  • Yea I know about collect but it involves me doing `manual` cast. There must be another way. Besides I have more than the 2 types of transactions that I show in the sample code. I'll check out the other answer. – Adrian Dec 02 '17 at 19:17
  • 1
    In that case I would recommend making the base trait (`OptionTransaction`) `sealed` and check [Shapeless' coproducts](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#coproducts-and-discriminated-unions) to generate the generic implementation. – Gábor Bakos Dec 02 '17 at 19:41
  • I think I might go the coproduct way – Adrian Dec 03 '17 at 19:31