1

I am new in Scala and I am referring book "Scala for Impatient - Second Edition".

I am working on a small code where I have created a class Fraction which as two Int fields num (number) and den(denominator). The class has a method * which performs multiplication of num and den and returns new Fraction instance.

Here, for understanding the working of implicit, I have created an object FractionConversions which helps with two implicit method intToFraction and fractionToDouble.

While testing the code in ImplicitConversionTester, I have imported the FractionConversions._ path, so both of the implicit methods are available to compiler.

Now, refer the code to make the picture more clear.

package OperatorOverloadingAndImplicitConversion class Fraction(n : Int, d : Int) { private val num = this.n private val den = this.d def *(other : Fraction) = new Fraction(num*other.num, den*other.den) override def toString: String = { s"Value of Fraction is : ${(num*0.1)/den}" } } object Fraction { def apply(n: Int, d: Int): Fraction = new Fraction(n, d) } object FractionConversions { implicit def fractionToDouble(f : Fraction): Double = { println("##### FractionConversions.fractionToDouble called ...") (f.num*0.1)/f.den } implicit def intToFraction(n : Int) : Fraction = { println("##### FractionConversions.intToFraction called ...") new Fraction(n,1) } } object ImplicitConversionTester extends App { import FractionConversions._ /* * CASE 1 : Here, "fractionToDouble" implicit is called. Why "intToFraction" was eligible but not called ? */ val a = 2 * Fraction(1,2) /* * CASE 2 : Works as expected. Here, number "2" is converted to Fraction using "intToFraction" implicit. */ val b = 2.den /* * CASE 3: Why again "intToFraction" but not "fractionToDouble" ? Why ? */ val c = Fraction(4,5) * 3 println(a) // output : 0.1 FractionConversions.fractionToDouble called println(b) // output : 1 FractionConversions.intToFraction called println(c) // output : Value of Fraction is : 0.24000000000000005. FractionConversions.intToFraction called } 

I have query in above code :

  • Case#1 : For statement val a = 2 * Fraction(1,2), Why fractionToDouble implicit is getting called here even though intToFraction is also eligible in this case ?

  • case#3 : For statement val c = Fraction(4,5) * 3, why intToFraction called ? Why fractionToDouble was not used ?

Here, I tried to replicate the following scenario mentioned in the book, so above question arises. enter image description here

So, should We summarise that, the compiler avoids converting the left side operand and select the right side to convert if both of them are eligible for conversion ? For example, In case of (a*b), a is always ignored and b is converted to expected type even though a and b both are eligible for conversion ?

9
  • 4
    Stay away from implicit conversions whenever possible, they lead to confusing code and can make the compiler generate slower or wrong code. Rather prefer two extension methods: toFraction on Int and toDouble on Fraction Commented Aug 22, 2021 at 17:47
  • 1
    @LuisMiguelMejíaSuárez Why should toDouble be an extension method if the OP owns the code of Fraction? Also, implicit class IntOps(n: Int) { def toFraction: Fraction = new Fraction(n,1) } is just a sugar for class IntOps(n: Int) { def toFraction: Fraction = new Fraction(n,1) }; implicit def intToOps(n: Int): IntOps = new IntOps(n), so you actually can't stay absolutely away from implicit conversions. Commented Aug 22, 2021 at 19:08
  • @DmytroMitin yeah the first was mostly bad wording on my side, and yes while technically speaking extension methods are implemented using implicit conversions under the hood, both are different patterns and you know that. Is like saying that one can not stay away of go-to because if and while are implemented at the byte code as those, or that you can't stay away from loops because map is implemented using those, or that you can't stay away of machine code because everything will be executed by a CPU. Commented Aug 22, 2021 at 19:39
  • 2
    @DmytroMitin I understand and totally agree, but my point still stands; while you are technically correct, practice says otherwise. Since extension methods require the explicit invocation of the extension method the compiler search is faster and safer than a general conversion from two unrelated types. In any case, this is not my point alone, a similar point can be found in the stdlib docs of previous Scala versions where the scala.collection.JavaConversions object still existed but was deprecated: scala-lang.org/files/archive/api/2.12.13/scala/collection/… Commented Aug 22, 2021 at 20:31
  • 1
    @GunjanShah this is what I mean with using extension methods instead: scastie.scala-lang.org/BalmungSan/OUHrKHIzR8CVdaR69F7vQw - You may also provide an overload of Fraction.* to accept doubles and another extension method * in the IntOps class to get the same API you wanted. Commented Aug 23, 2021 at 15:30

1 Answer 1

2

Your characterization is correct: for an expression a.f(b) (note that a*b is just operator notation for a.*(b)), an implicit conversion applicable to b which allows the expression to type check will take precedence over an implicit conversion applicable to a. This arises because an implicit conversion (called a "view" in the language standard) of a could only be attempted if * (in Int) was not applicable to Fraction (see SLS 7.3)... however the definition of applicability in SLS 6.6 says that an implicit view is sufficient to be applicable.

Accordingly, if one wanted to implicitly convert 2 to a Fraction to use Fraction.*, one would need

(2: Fraction) * Fraction(1, 2) 

Likewise, for case 3, if you want the fraction implicitly converted to a Double first:

(Fraction(4,5): Double) * 3 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.