1

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
}
fracca
  • 2,417
  • 1
  • 23
  • 22
  • What's the definition of `Monoid`? If I define it as `trait Monoid[M] { def zero: M; def append(m1: M, m2: M): M }` then I couldn't get your code to compile without changing the type of `fuelMonoid` to `implicit def fuelMonoid[F <: FuelType](implicit fuelType: F) = new Monoid[Fuel[F]]`. Then your `add` example works. – Lee May 08 '14 at 14:15
  • The Monoid trait is indeed defined like that, i'll add the code in the main question. Adding new Monoid[Fuel[F]] insted of new Monoid[Fuel] didn't make a difference, for the same reason that the add(x,y) doesn't work. Any suggestions? – fracca May 08 '14 at 14:24

1 Answers1

0

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.

tkroman
  • 4,811
  • 1
  • 26
  • 46
  • something quite bizarre is going on. Intellij Idea reports all kinds of errors. Using Scalaz, i was able to take advantage of the second parameter been a call by name, where my version requires a strict parameter. I'll keep tinkering with it to try to get it to work, but it's good to know that the solution should work anyways. – fracca May 08 '14 at 14:53
  • Indeed. the problem is Intellij IDEA. The Type Aware Highlighter must have some bug. By the way, do you know how to make a fold like operation using the append you suggested: trait Monoid[A] { def append(a1: A, a2: => A): A def zero: A } object Monoid { def fold[A](as: Seq[A], m: Monoid[A]): A = as.foldLeft(m.zero)(m.append) }. I'd rather not use the full Scalaz for just this. Thanks! – fracca May 08 '14 at 14:58
  • @fracca, sorry, I didn't notice that you use your own `Monoid`. I get the same weird errors with non-Scalaz trait as you do. I'll try to tinker with it a little more. – tkroman May 08 '14 at 15:04
  • @fracca, looks like I've found an answer to both of your questions. I edited the answer (added simple `Monoid` trait and `Monoid` object with `fold`). – tkroman May 08 '14 at 15:19