Question

The problem is simple, I have an object which does a binary operation with two parameters. I want to only add fuels that have the same type, like this:

object Fuels {

  case class Fuel[F <: FuelType](amount: Double, fuelType: F = Petrol) {
    def +(that : Fuel[F]) = {
      copy(amount = this.amount + that.amount)
    }
  }

  def add[F <: FuelType](x: Fuel[F], y: Fuel[F]): Fuel[F] = x + y

  sealed trait FuelType {
    val name : String
  }
  case object Petrol extends FuelType{
    override val name = "Petrol"
  }
  case object Diesel extends FuelType{
    override val name = "Diesel"
  }
  case object Hydrogen extends FuelType{
    override val name = "Hydrogen"
  }

 implicit def fuelMonoid[F <:FuelType](implicit fuelType: F) = new Monoid[Fuel]{
     override def zero: Fuel[F] = Fuel(0, fuelType)
     override def append(m1: Fuel[F], m2: Fuel[F]) : Fuel[F] = m1 + m2
   }

}

Use it:

> Fuel(10, Petrol) + Fuel(20, Petrol)
> add(Fuel(10, Petrol), Fuel(10, Petrol))

Compilation error:

Expression of Type Fuels.Fuel[Nothing] does not conform to Fuels.Fuel[F]

The main issue is that both add and fuelMonoid fail to identify that we are dealing with items of the same type. The compiler can't resolve the type constraint, and infers Nothing.

For completion, here's Monoid, nothing extraordinary:

trait Monoid[A] {
  def zero: A
  def append(a1: A, a2: A): A
}
Was it helpful?

Solution

I changed type to Monoid[Fuel[F]], specified return type of implicit def and Fuel#+ and it works:

object Fuels {

  trait Monoid[F] {
    def zero: F
    def append(f1: F, f2: F): F
  }

  object Monoid {
    def fold[F](as: Seq[F], m: Monoid[F]): F = as.foldLeft(m.zero)(m.append)
  }

  case class Fuel[F <: FuelType](amount: Double, fuelType: F = Petrol) {
    def +(that : Fuel[F]): Fuel[F] = {
      copy(amount = this.amount + that.amount)
    }
  }

  def add[F <: FuelType](x: Fuel[F], y: Fuel[F]): Fuel[F] = x + y

  sealed trait FuelType {
    val name : String
  }
  case object Petrol extends FuelType{
    override val name = "Petrol"
  }
  case object Diesel extends FuelType{
    override val name = "Diesel"
  }
  case object Hydrogen extends FuelType{
    override val name = "Hydrogen"
  }

  implicit def fuelMonoid[F <:FuelType](implicit fuelType: F): Monoid[Fuel[F]] = new Monoid[Fuel[F]] {
    override def zero: Fuel[F] = Fuel(0, fuelType)
    override def append(m1: Fuel[F], m2: Fuel[F]): Fuel[F] = m1 + m2
  }
}

object Main {
  def main (args: Array[String] ) {
    import Fuels._
    println(Fuel(10, Petrol) + Fuel(20, Petrol))
    println(add(Fuel(10, Petrol), Fuel(20, Petrol)))
    println(Monoid.fold(Seq(Fuel(10, Petrol), Fuel(20, Petrol), Fuel(30, Petrol)), fuelMonoid(Petrol)))
  }
}

http://ideone.com/kRocck

Upd1. But here is more interesting example: http://ideone.com/QKuCad. As you see, to make it work correctly I had to define the implicit value of a specified type (line 44). Is there any way to make this procedure more automated? (I mean, is there any way to make fuelMonoid acceptable as an implicit of any concrete FuelType subtype?)

Upd2. Yes, in fact there is: http://ideone.com/UEaFRj.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top