Manu Chadha Manu Chadha - 1 month ago 8
Scala Question

'implicit' in scala not working as thought

In order to understand how 'implicit' works in Scala, I have written following code but it doesn't give me the expected result. Could someone please let me know what am I doing wrong here

I (thought) I understand the use of 'implicit' (which clearly is not true). I have three classes, Plant, Dog and myPet. Dog has a function 'sound' which returns "woof". Plant has a function "tallOrShort" which returns "tall". These functions describe features of Dog and Plant.

I want to write a generic code in myPet to which I can pass an instance of Dog or Plant. I call this function 'describe'. The describe function should print "woof" for Dog instances and "tall" for Plant instances.

Dog and Plant are independent classes (no subtype). I thought I can use 'implicit' to "add" this functionality in Dog and Plant class. My understanding is that by using implicit, Dog or Plant could be implicitly converted into 'something' which can then call 'sound' or 'tallOrShort' for Dog and Plant respectively. I have written following code but it doesnt work.

I started with Dog class to begin with

//create Dog
scala> class Dog {
| def sound = "woof"
| }

defined class Dog

//create trait (the contract interface which Dog can be member of
scala> trait myPetTrait[A] {
| def describePet(a:A):String
| }

defined trait myPetTrait

//implicit conversion should happen using this code?
scala> implicit object dogIsPet extends myPetTrait[Dog] {
| def describePet(d:Dog) = d.sound
| }

defined object dogIsPet


Now first, I thought of defining only a generic function 'describe' as follows but I am not able to pass Dog or Plant instance to it.

//'describe' function. It should work for both Dog and Plant
scala> def describe[A](implicit pt:myPetTrait[A]) = {println(pt.describePet(_:A))}
describe: [A](implicit pt: myPetTrait[A])Unit

scala> describe(new Dog)
<console>:21: error: type mismatch;
found : Dog
required: myPetTrait[?]
describe(new Dog)


Question 1 - What is wrong with above? Shouldn't Dog get converted to myPetTrait[Dog]?

Then I thought to create a class (myPet) and define 'describe' in that class. That doesnt work either

//metPet It should work for both Dog and Plant
scala> class myPet[A](a:A) {
| def describe[A](implicit pt:myPetTrait[A]) = {println(pt.describePet(_:A))}
| }
defined class myPet

scala> new myPet(new Dog).describe
<function1>


Question 2 - Why does this compile at least if I put describe in myPet? Why am I getting a function literal (function1), not the print I expect

To keep experimenting, I started REPL again and declared Plant as well before creating describe and myPet

//new REPL session. created Dog
scala> class Dog {
| def sound = "woof"
| }
defined class Dog

//created plant
scala> class Plant {
| def tallOrShort = "tall"
| }

defined class Plant
//created trait
scala> trait myPetTrait[A] {
| def describePet(a:A):String
| }

defined trait myPetTrait

//code which should I think help in implicit conversion
scala> implicit object plantIsPet extends myPetTrait[Plant] {
| def describePet(p:Plant) = p.tallOrShort
| }
defined object plantIsPet

//describe still doesn't work
//describe shuold work for both Plant and Animal
scala> def describe[A](implicit pt:myPetTrait[A]) = {println(pt.describePet(_:A))}
describe: [A](implicit pt: myPetTrait[A])Unit

//not sure why this error comes
scala> describe(new Dog)
<console>:21: error: type mismatch;
found : Dog
required: myPetTrait[?]
describe(new Dog)

//get ambiguity error in this line
scala> new myPet(new Dog).describe //myPet gives ambiguity error


I get ambiguous implicit values eror stating that both object dogIsPet of type dogIsPet.type and object plantIsPet of type plantIsPet.type match expected type myPetTrait[A]

Question 3 - why does scala complain of ambiguity? It is probably because dogIsPet.type and plantisPet.type are of same 'type'. How do I make this code work?

thanks

Answer

I think you missed this (describe must take a of type A and an implicit contract)

define describe like this in myPet

class myPet[A](a:A) {
 def describe(implicit pt:myPetTrait[A]) = {
   println(pt.describePet(a))
 }
}

describe outside the myPet can be declared like this

def describe[A](a: A)(implicit pt:myPetTrait[A]) = {println(pt.describePet(a))}

Scala has a special syntax for this

def describe[A: myPetTrait](a: A) = println(implicitly[myPetTrait[A]].describePet(a))

Scala REPL

scala> def describe[A](a: A)(implicit pt:myPetTrait[A]) = {println(pt.describePet(a))}
describe: [A](a: A)(implicit pt: myPetTrait[A])Unit

scala> describe[Plant](new Plant)
tall

or use scala syntactic sugar

scala> def describe[A: myPetTrait](a: A) = println(implicitly[myPetTrait[A]].describePet(a))
describe: [A](a: A)(implicit evidence$1: myPetTrait[A])Unit

scala> describe[Plant](new Plant)
tall

scala> trait myPetTrait[A] { def describePet(a:A):String } 
defined trait myPetTrait

scala> class Dog { def sound = "woof" } 
defined class Dog

scala> implicit object dogIsPet extends myPetTrait[Dog] { def describePet(d:Dog) = d.sound }  
defined object dogIsPet

scala> def describe[A: myPetTrait](a: A) = println(implicitly[myPetTrait[A]].describePet(a)) 
defined function describe

scala> implicit object dogIsPet extends myPetTrait[Dog] { def describePet(d:Dog) = d.sound }  
defined object dogIsPet

scala> describe[Dog](new Dog) 
woof

scala> class myPet[A](a:A) {
      def describe(implicit pt:myPetTrait[A]) = {
        println(pt.describePet(a))
      }
    } 
defined class myPet

scala> new myPet[Dog](new Dog).describe 
woof

Complete code at one place

Main.scala

object Implicits {
  trait myPetTrait[A] {
    def describePet(a:A):String
  }

  class Dog {
    def sound = "woof"
  }

  class Plant {
    def tallOrShort = "tall"
  }

  //evidence for Plant
  implicit object plantIsPet extends myPetTrait[Plant] {
    def describePet(p:Plant)  = p.tallOrShort
  }

  //evidence for Dog
  implicit object dogIsPet extends myPetTrait[Dog] {
    def describePet(d:Dog) = d.sound
  }

  def describe[A](a: A)(implicit pt:myPetTrait[A]) = {
    println(pt.describePet(a))
  }

  //syntactic sugar
  def describe2[A : myPetTrait](a: A) =
    println(implicitly[myPetTrait[A]].describePet(a))

  class myPet[A](a:A) {
    def describe(implicit pt:myPetTrait[A]) = {
      println(pt.describePet(a))
    }
  }

}


object Main {

  import Implicits._

  def main(args: Array[String]): Unit = {
    describe(new Dog)
    describe(new Plant)
    describe2(new Dog)
    describe2(new Plant)

    new myPet[Dog](new Dog).describe
    new myPet[Plant](new Plant).describe
  }
}

output:

woof
tall
woof
tall
woof
tall
Comments