3

I know view bounds may be deprecated soon. Please ignore that.

The following code compiles if only one of the last 3 implicit conversions are uncommented. Is this a compiler bug?

object Viewable extends App {
  /** Speed of light in m/s */
  val C: Double = 299293458d

  /** @param weight in kilograms */
  case class Matter(name: String, weight: Double) {
    /** @return matter-energy equivalence in megajoules */
    def energy: Double = weight * C * C / 1000000d

    def megaJouleMsg: String = f"$name's mass-energy equivalence is $energy%.0f megajoules."
  }

  case class Animal(name: String, height: Double, weight: Double)
  case class Vegetable(name: String, height: Double, weight: Double)
  case class Mineral(name: String, weight: Double)

  case class Bug(name: String, height: Double, weight: Double, canFly: Boolean)
  case class Whale(name: String, height: Double, weight: Double, hasTeeth: Boolean)

  case class AppleTree(name: String, height: Double, weight: Double, age: Int)
  case class Grass(name: String, height: Double, weight: Double, edible: Boolean)

  case class Sand(name: String, color: String, weight: Double)
  case class Rock(name: String, color: String, weight: Double)

  implicit def sandToMineral(sand: Sand) = Mineral(sand.name, sand.weight)
  implicit def rockToMineral(rock: Rock) = Mineral(rock.name, rock.weight)

  implicit def appleTreeToVegetable(tree: AppleTree) = Vegetable(tree.name,  tree.height,  tree.weight)
  implicit def grassToVegetable(grass: Grass)        = Vegetable(grass.name, grass.height, grass.weight)

  implicit def bugToAnimal(bug: Bug)       = Animal(bug.name, bug.height, bug.weight)
  implicit def whaleToAnimal(whale: Whale) = Animal(whale.name, whale.height, whale.weight)

  implicit def animalToMatter[X <% Animal](animal: X)          = Matter(animal.name,    animal.weight)
  implicit def vegetableToMatter[X <% Vegetable](vegetable: X) = Matter(vegetable.name, vegetable.weight)
  implicit def mineralToMatter[X <% Mineral](mineral: X)       = Matter(mineral.name,   mineral.weight)

  println(Animal("Poodle", 1.0, 8.0).megaJouleMsg)
  println(AppleTree("Spartan", 2.3, 26.2, 12).megaJouleMsg)
  println(Rock("Quartz crystal", "white", 2.3).megaJouleMsg)
}

The error messages are:

type mismatch;
 found   : solutions.Viewable.Animal
 required: ?{def megaJouleMsg: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method animalToMatter in object Viewable of type [X](animal: X)(implicit evidence$1: X => solutions.Viewable.Animal)solutions.Viewable.Matter
 and method vegetableToMatter in object Viewable of type [X](vegetable: X)(implicit evidence$2: X => solutions.Viewable.Vegetable)solutions.Viewable.Matter
 are possible conversion functions from solutions.Viewable.Animal to ?{def megaJouleMsg: ?}
  println(Animal("Poodle", 1.0, 8.0).megaJouleMsg)
                ^

type mismatch;
 found   : solutions.Viewable.AppleTree
 required: ?{def megaJouleMsg: ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method animalToMatter in object Viewable of type [X](animal: X)(implicit evidence$1: X => solutions.Viewable.Animal)solutions.Viewable.Matter
 and method vegetableToMatter in object Viewable of type [X](vegetable: X)(implicit evidence$2: X => solutions.Viewable.Vegetable)solutions.Viewable.Matter
 are possible conversion functions from solutions.Viewable.AppleTree to ?{def megaJouleMsg: ?}
  println(AppleTree("Spartan", 2.3, 26.2, 12).megaJouleMsg)
                   ^
Mike Slinn
  • 7,705
  • 5
  • 51
  • 85

1 Answers1

1

Well the compiler gives the error precisely. This is because there is more than one function which can Animal => Matter. i.e. your below 3 functions:

  implicit def animalToMatter[X](animal: X) (implicit ev: X => Animal) =  Matter(animal.name,    animal.weight)
  implicit def vegetableToMatter[X ](vegetable: X) (implicit ev: X => Vegetable) = Matter(vegetable.name, vegetable.weight)
  implicit def mineralToMatter[X ](mineral: X) (implicit ev: X => Mineral)  = Matter(mineral.name,   mineral.weight)

are equally eligible to be called when you do Animal("Poodle", 1.0, 8.0).megaJouleMsg.

When you call megaJouleMsg the compiler looks for any implicit function available that can take Animal and return an object containing method megaJouleMsg (Matter in your case). Now all the 3 functions can take Animal (there is no constraint coded any where) and can return Matter. So the compiler gets confused on which function to calls

Solution: From what it appears, the view bound is not required. This will do:

  implicit def animalToMatter(animal: Animal)  = Matter(animal.name,    animal.weight)
  implicit def vegetableToMatter(vegetable: Vegetable) = Matter(vegetable.name, vegetable.weight)
  implicit def mineralToMatter(mineral: Mineral)       = Matter(mineral.name,   mineral.weight)

  scala> Animal("Poodle", 1.0, 8.0).megaJouleMsg
  res1: String = Poodle's mass-energy equivalence is 716612592013 megajoules.

Edit: Looks like the confusion is because of X <% Animal. In animalToMatter function, it expects an implicit argument which can take X and return Animal. Now if you see in your scope there is no such function X => Animal. But because you are passing Animal("Poodle", 1.0, 8.0) it doesn't need any implicit function because it has already obtained Animal.

In short to repeat the compiler process on seeing Animal("Poodle", 1.0, 8.0).megaJouleMsg:

  1. Sees all functions in the current scope which can take Animal and return Matter.
  2. All the 3 functions can take Animal and return Matter. Note that vegetableToMatter and mineralToMatter will fail if accepted. i.e. though they are eligible but they will not succeed because they dont have any implicit function available that can X => Animal
  3. Throws Error

As an example consider this:

scala> implicit def f[T <% Int](n:T) = Matter("",1)  
warning: there were 1 feature warning(s); re-run with -feature for details
f: [T](n: T)(implicit evidence$1: T => Int)Matter

scala> 1.megaJouleMsg
res2: String = 's mass-energy equivalence is 89576574002 megajoules.

scala> "a".megaJouleMsg          
<console>:12: error: No implicit view available from String => Int.
              "a".megaJouleMsg          
              ^
<console>:12: error: value megaJouleMsg is not a member of String
              "a".megaJouleMsg      

Notice the error? It gives:

No implicit view available from String => Int.

and not just value megaJouleMsg is not a member of String

Which simply means "a" was eligible for the implicit function f but f could not find a function that could convert it to Int and hence it gives error No implicit view available.... If it was not eligible for function f, then it would just throw error megaJouleMsg is not a member...

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Jatin
  • 31,116
  • 15
  • 98
  • 163
  • You are correct about view bounds not being required. However, I do not understand how vegetableToMatter or mineralToMatter are considered eligible. – Mike Slinn Mar 04 '14 at 14:08
  • @MikeSlinn Because all of them can take input a generic type `X` and all return `Matter` type. Hence. Had it been `implicit def animalToMatter[X <: Animal](animal: X) :Matter = {Matter(animal.name, animal.weight)} ` and so for `vegetable` etc, then things work as then there is a bound and restriction on the type – Jatin Mar 04 '14 at 16:44
  • I thought X was either an Animal, or a subclass of Animal, or a Vegetable or subclass of Vegetable, or a Mineral or a subclass or Mineral. Also there is no relationship between Animal, Vegetable and Mineral. So how does an instance of an Animal get viewed as a Vegetable or a Mineral? – Mike Slinn Mar 05 '14 at 05:03
  • That is because no where in the code you have defined constraint that `X` should be `Animal`. Just defining `animalToMatter[X <% Animal](animal: X)` doesn't add any constraint on `X`. `X` can still be any generic type which can accept any type of object. – Jatin Mar 05 '14 at 05:19
  • @MikeSlinn I still do not understand the confusion? Is it because of `X <% Animal`? – Jatin Mar 05 '14 at 06:25
  • Edited. It. I do not know what more I can do to explain :( – Jatin Mar 05 '14 at 06:39
  • I thought that a view bound imposed a constraint. Otherwise, what is the point of a view bound? – Mike Slinn Mar 05 '14 at 16:07
  • Nope it doesn't add any constraint on type. Refer this http://stackoverflow.com/questions/4465948/what-are-scala-context-and-view-bounds . I think I will need bounty for more on it :-) – Jatin Mar 05 '14 at 16:49