0

I am reading a Spark in Action book, I came across a construct that even after a while working with Scala I still can't understand. Here is the code (complete code) :

First we have a case class where RDD will be converted to DataFrame using this case class:

     import java.sql.Timestamp
        case class Post(
          commentCount:Option[Int],
          lastActivityDate:Option[java.sql.Timestamp],
          ownerUserId:Option[Long],
          body:String,
          score:Option[Int],
     ... ) 

Then we define an object with implicit class

object StringImplicits {

    implicit class StringImprovements(val s: String) {
    import scala.util.control.Exception.catching

    def toIntSafe = catching(classOf[NumberFormatException]) opt s.toInt

    def toLongSafe = catching(classOf[NumberFormatException]) opt s.toLong

    def toTimestampSafe = catching(classOf[IllegalArgumentException]) opt
      Timestamp.valueOf(s)
  }

}

Then we import the object which is already in scope for some reason and now looks like all String object has the three methods defined in StringImprovements

import StringImplicits._

def stringToPost(row: String): Post = {
  val r = row.split("~")
  Post(r(0).toIntSafe,
    r(1).toTimestampSafe,
    r(2).toLongSafe,
    r(3),
    ... 
}

Questions:

  • Why do we need to need to import an already in scope object?
  • Which statement exactly assigned these methods to the String object, this is so confusing?

Reading implicit classes doc it makes some sense but not everything is clear. For example how come this is a valid expression

scala> 5 times println("HI")

I understand if it was

IntWithTimes(5).time(println("HI")) 
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Adelin
  • 18,144
  • 26
  • 115
  • 175
  • You are not importing the object, your are importing the contents of the object; specifically the implicit class. - A implicit class is a mechanism to add **extension methods** meaning, adding additional methods to an existing class; you do not have to understand the underlying implicit mechanism but if you want Mateusz's answer does in great detail. Bur, you may just abstract over that a see that since the implicit class wraps a String then it is adding those methods to the String type. - BTW, a recommendation, do not abuse of the fact that you can omit the dot and parenthesis. – Luis Miguel Mejía Suárez Jan 22 '21 at 13:23
  • Finally, it would be better if that implicit class is also a value class to ensure there won't be any real instantiation when calling an extension method. - `implicit class StringImprovements (private val s: String) extends AnyVal {` – Luis Miguel Mejía Suárez Jan 22 '21 at 13:24

1 Answers1

1

Implicits are sought among all methods and values that are visible in the current scope AND within companion objects of types that appear in the implicit type you are looking for.

implicit val a: A = ???

implicit val bc: B[C] = ???

// code here sees a and bc in implicit scope

def helper = {

  implicit val c: C = ???

  // code here sees a and bc and c in implicit scope
}

def anotherHelper = {

  val a: A = ??? // overshades a
  
  // code here sees b in implicit scope
}

object Helper {

  implicit val e: E = ???

  // code here sees a, bc and e in implicit scope
}

// implicits within helper, anotherHelper and Helper are not seen here
// though Helper one's can be imported with import Helper._
class D[T]
object {

  implicit def provide[T](implicit a: T): D[T] = ???
}

class E
object E {
  implicit val e: E = ???
}

class F
object F

// sees no implicits that are always available but

// if you require implicitly[E], E companion will be searched
// and implicit e will be found

// if you require implicitly[D[F]], both D and F companions
// will be searched, D.provide[F] will require implicit F,
// and no such implicit will be found - failure 

If you asked for implicit A[B, C, (D, E), F[G]], then Scala would look into companions of A, B, C, D, E, Tuple2, F, G (if they exist). If this rule doesn't apply (your implicits aren't in companion, your type doesn't appear in sought implicit) you have to import them manually.

When Scala sees that you try to call a method on object that doesn't exists, it will try to convert it to some object which has this method. If you have some X type, it will look for implicits your X and returning some Y (implicit defs(x: X): Y and implicit val f: X => Y) and checking if that Y has this method.

Implicit class is basically a combination of a class and implicit method:

// implicit class Y(x: X) is the same as
class Y(x: X)
implicit def convert(x: X): Y = new Y(x)

which means that you can add some implicit conversions/classes from type X into X's companion and they will be always available.

In docs:

implicit class IntWithTimes(x: Int)

created implicit conversion from Int => IntWithTimes. String doesn't have times method, but there is a conversion Int => IntWithTimes in implicit scope imperted with import Helper._ (without it compilation would fail). Its result, IntWithTimes has the times method with the right signature, so the conversion is used.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64