1. Objective

In this article, we’re going to cover implicit imports in Scala. We’ll cover what they are and then go into a bit of detail about the Predef package.

2. Implicit Imports

Implicit imports are the imports that are available by default to every compilation unit or Scala program without the programmer having to import them explicitly. Only a few packages are implicitly imported:

import java.lang._
import scala._
import Predef._

We’ll cover them separately later in this article.

3. :import Command

We can easily verify all the implicit imports by running the :import command on the Scala console:

scala> :import
1) import java.lang._ (136 types, 144 terms)
2) import scala._ (179 types, 167 terms)
3) import scala.Predef._ (100 terms, 58 are implicit)

We see that running :import in the console shows that all three packages are imported by default.

4. Package java.lang

Java programmers would know that java.lang is always imported by default in Java programs. This is pretty much an artifact of Scala’s roots in Java. The package provides classes that are fundamental to Java, like Object and Class. It also contains wrapper classes for all primitive types.

The Math class provides methods for commonly used mathematical functions like square root and log, whereas the String and StringBuilder classes provide common operations on strings.

Classes such as ClassLoader, Process, and SecurityManager provide system operations to enable dynamic loading and managing host environments.

And, the Throwable class contains the objects that may be thrown as exceptions.

5. Package scala

Package scala._ is the next import. Unlike Java, it’s unique in that it overrides preceding imports coming from the java.lang package. For instance, scala.StringBuilder overrides java.lang.StringBuilder. Read more about the Scala package at ScalaDoc.

6. Package Predef

The Predef object contains types, implicit conversion among types, and utility methods. This import automatically puts the scala.Predef object in program scope. All members of the object are available to the program.

Let’s look at the major parts of this package.

6.1. Utility Methods

Predef provides six utility methods. Let’s have a look at them.

??? is used to denote methods that aren’t implemented yet. Calling any such would throw in NotImplementedError. Let’s see how it works:

def notImplemeted:Int => Boolean = ???
assertThrows[NotImplementedError] {
  notImplemeted
}

Here, we’ve assigned the method notImplemented to ??? and asserted that calling it would raise NotImplementedError.

The classOf method retrieves the runtime representation of a class type. classOf[T] is equivalent to the class literal T.class in Java. Let’s assert what the classOf String type is:

assert("class java.lang.String" == classOf[String].toString)

The identity method returns a value that is equal to the input value and of the same type:

assert("some" == identity("some"))

Calling identity with String input “some” returns the same value and type.

The implicitly method returns the implicit value of type T:

implicit val a = "test"
assert("test" == implicitly[String])

We declared a String type with the value of “test”. Calling implicitly with a String type returns the original declared value.

The locally method is pretty much another name for identity. It’s used to mark a code block as an expression rather than it being taken as an anonymous class. The main idea of this is to avoid a dangling local code block. Let’s see how it works:

object Local {
  object A
  {
    val a = 10
  }

  object B

  locally {
   val b = 20
  }
}

If not for locally, the above wouldn’t compile, and the new line after object B would dissociate the code block.

Finally, the valueOf method retrieves the single value of a type with a unique inhabitant:

assert(25 == valueOf[25])

6.2. Assertions

These are methods that support program verification and runtime correctness.

The assert method tests a boolean statement and throws an AssertionError if false. Let’s have a look:

assertThrows[AssertionError] {
  assert(2 + 2 == 5, "sum doesn't add up")
}

assertThrows[AssertionError] {
  assert(2 + 2 == 5)
}

The assume method tests an expression, throwing an AssertionError if false. This method is similar to assert but with changes in the intent expressed — assert contains a predicate that needs to be proven, while assume contains an axiom for a static checker. Let’s see how that works:

assertThrows[AssertionError] {
  assume(2 + 2 == 5, "sum doesn't add up")
}

assertThrows[AssertionError] {
  assume(2 + 2 == 5)
}

Finally, require tests an expression, throwing an IllegalArgumentException if false. This method is similar to assert, but puts the onus on the caller of the method for violating the condition:

assertThrows[IllegalArgumentException] {
  require(2 + 2 == 5, "sum doesn't add up")
}

assertThrows[IllegalArgumentException] {
  require(2 + 2 == 5)
}

6.3. Aliases

Predef has several aliases that bring immutable types into scope without imports. These are Class, Function, Map, Set, and String. These facilitate the use of immutable types by avoiding the risk of importing default mutable Java Map and Set classes:

type Class[T] = java.lang.Class[T]
type Function[-A, +B] = (A) => B
type Map[K, +V] = collection.immutable.Map[K, V]
type Set[A] = collection.immutable.Set[A]
type String = java.lang.String 
val ->: Tuple2.type 
val Map: collection.immutable.Map.type
val Set: collection.immutable.Set.type

6.4. String Conversions

String conversions make it simple to convert from String to Scala’s StringOps or WrappedString. Because of these imports, we can treat any java.lang.String object as a StringOps or WrappedString, allowing us to use more powerful methods that treat String as a collection:

implicit def augmentString(x: String): StringOps
implicit def wrapString(s: String): WrappedString

Consider this:

assert("ello" == "hello".filter(_ != 'h'))

We know that default java.lang.String does not have a filter method. We can use it here because it gets implicitly converted to a StringOps object.

6.5. Implicit Classes

There are also some useful extensions for the Any type:

implicit final class ArrowAssoc[A] extends AnyVal
implicit final class Ensuring[A] extends AnyVal
implicit final class StringFormat[A] extends AnyVal

Let’s see an example of how ensuring works:

def doubleInput(n:Int) Int = {
  n * 3
} ensuring(n => n % 2 == 0)

assertThrows[AssertionError] {
  doubleInput(3)
}

In the above example, calling doubleInput would throw AssertionError because the result is not divisible by 2.

6.6. CharSequence Wrappers

There are a handful of wrappers that implement implicit CharSequence classes:

final class ArrayCharSequence extends CharSequence
final class SeqCharSequence extends CharSequence
def ArrayCharSequence(arrayOfChars: Array[Char]): ArrayCharSequence
def SeqCharSequence(sequenceOfChars: collection.IndexedSeq[Char]): SeqCharSequence

6.7. Java to Scala Converters

These are implicit converters from all Java primitives to Scala equivalents. There is one method for each Java primitive type – boolean, byte, char, double, float, int, long, and short.

Let’s have a look at an example:

assert(false.compare(true) == -1)

We know that primitive boolean does not have compare method. It’s available because of the implicit conversion to Boolean.

6.8. Scala to Java Converters

These methods are the reverse of those in the previous section — converters from Scala AnyVals to Java primitives wrapper equivalents. Again, there’s one for each Java primitive type.

6.9. Array to ArraySeq Converters

Following the pattern, these are methods that do conversions from Java Array to Scala ArraySeq, one for each primitive type:

assert(Array(1,2,3).reverse.last == 1)

Here, we call the method reverse because of the implicit conversion of Array[Int] to ArraySeq[Integer].

6.10. Other Rich Converters

Finally, there are methods that covert Java types to equivalent rich Scala datatypes. Rich types extend the behavior of basic types, thus removing the need for verbose boilerplate code. For example, we can reverse a String:

assert(“xyz”.reverse == “yzx”)

Even though there’s no reverse method for String types, it’s possible because of implicit conversion to RichString.

7. Conclusion

In this article, we covered Scala’s implicit imports and looked deeper into Predef objects.

As always, all code examples can be found over on GitHub.

Comments are closed on this article!