Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the implicit resolution sequence in this "simple" ScalaZ tutorial code example?

The code snippet below is taken from this ScalaZ tutorial.

I cannot figure out how the implicit resolution rules are applied when evaluating 10.truthy at the bottom of the code example.

Things that - I think - I do understand are the following:

1) The implicit value intCanTruthy is an instance of an anonymous subclass of CanTruthy[A] which defines the truthys method for Int-s according to :

scala> implicit val intCanTruthy: CanTruthy[Int] = CanTruthy.truthys({
         case 0 => false
         case _ => true
       })
intCanTruthy: CanTruthy[Int] = CanTruthy$$anon$1@71780051

2) The toCanIsTruthyOps implicit conversion method is in scope when evaluating 10.truthy, so the compiler will try to use this implicit conversion method when it sees that Int does not have a truthy method. So the compiler will try to look for some implicit conversion method which converts 10 into an object that does have a truthy method and therefor it will try toCanIsTruthyOps to this conversion that.

3) I suspect that the implicit value intCanTruthy somehow might be used when the compiler tries the toCanIsTruthyOps implicit conversion on 10.

But this is where I really get lost. I just don't see how the implicit resolution process proceeds after this. What happens next ? How and Why ?

In other words, I don't know what is the implicit resolution sequence that allows the compiler to find the implementation of the truthy method when evaluating 10.truthy.

Questions:

How will 10 be converted to some object which does have the correct truthy method ?

What will that object be ?

Where will that object come from?

Could someone please explain, in detail, how the implicit resolution takes place when evaluating 10.truthy ?

How does the self-type { self => ... in CanTruthy play a role in the implicit resolution process ?

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait CanTruthy[A] { self =>
  /** @return true, if `a` is truthy. */
  def truthys(a: A): Boolean
}
object CanTruthy {
  def apply[A](implicit ev: CanTruthy[A]): CanTruthy[A] = ev
  def truthys[A](f: A => Boolean): CanTruthy[A] = new CanTruthy[A] {
    def truthys(a: A): Boolean = f(a)
  }
}
trait CanTruthyOps[A] {
  def self: A
  implicit def F: CanTruthy[A]
  final def truthy: Boolean = F.truthys(self)
}
object ToCanIsTruthyOps {
  implicit def toCanIsTruthyOps[A](v: A)(implicit ev: CanTruthy[A]) =
    new CanTruthyOps[A] {
      def self = v
      implicit def F: CanTruthy[A] = ev
    }
}

// Exiting paste mode, now interpreting.

defined trait CanTruthy
defined module CanTruthy
defined trait CanTruthyOps
defined module ToCanIsTruthyOps

Trying out the type class on 10 :

scala> import ToCanIsTruthyOps._
import ToCanIsTruthyOps._

scala> implicit val intCanTruthy: CanTruthy[Int] = CanTruthy.truthys({
         case 0 => false
         case _ => true
       })
intCanTruthy: CanTruthy[Int] = CanTruthy$$anon$1@71780051

scala> 10.truthy
res6: Boolean = true
like image 928
jhegedus Avatar asked Feb 12 '23 16:02

jhegedus


1 Answers

First of all, thanks for pasting a fully self-contained example.

How will 10 be converted to some object which does have the correct truthy method ?

When calling a method on a value whose type A does not supply that method, an implicit conversion must kick in, i.e. there must be a method or function in scope with signature A => B with B having the method in question (truthy). In the case of a conversion method, it may ask for additional implicit parameters which would be looked up accordingly.

The conversion method is toCanIsTruthyOps, made available by importing the contents of ToCanIsTruthyOps. The type B in the aforementioned sentence then is CanTruthyOps, and the conversion method is toCanIsTruthyOps. It may be invoked by the compiler as long as the implicit type-class evidence parameter CanTruthy is found. So since type A = Int, the compiler has to find an implicit value of type CanTruthy[Int] if the call 10.truthy is going to be successful.

It looks for such a value in several places. It could be in the companion object of Int (that doesn't exist) or the companion object of CanTruthy, or it was explicitly imported into the current scope. Here, the last case is used. You explicitly created the implicit value intCanTruthy, and that value is now found. And that's it.

What will that object be ?

There will be a temporary instance of CanTruthyOps whose mere purpose is to call truthys on the evidence type class value (your intCanTruthy here).

Where will that object come from?

It is found in the look-up for implicit conversions Int => CanTruthyOps[Int]. The conversion performs the instantiation of that object.

Could someone please explain, in detail, how the implicit resolution takes place when evaluating 10.truthy ?

See answer to first question above. Or as explicit code:

type A = Int
val v: A = 10
val ev: CanTruthy[A] = intCanTruthy
val ops: CanTruthyOps[A] = ToCanIsTruthyOps.toCanIsTruthyOps(v)(ev)
ops.truthy

How does the self-type { self => ... in CanTruthy play a role in the implicit resolution process ?

It has nothing to do with the implicit resolution. Indeed, in your example of the trait CanTruthy, the self acts as an alias for this and isn't even used, so you could just remove it.

like image 94
0__ Avatar answered Apr 26 '23 23:04

0__