DEV Community

Cover image for 10 Most Asked Scala Programming Questions
KiranPoudel98 for Truemark Technology

Posted on • Originally published at

10 Most Asked Scala Programming Questions

Scala is a general-purpose programming language that is mainly used for functional programming. It is object-oriented and it runs on JVM (Java Virtual Machine). Scala provides language interoperability with Java. So, today we will be checking out the 10 most asked Scala programming questions.

10 Most Asked Scala Programming Questions

1. Difference between object and class in Scala


  • class C defines a class, just as in Java or C++.
  • object O creates a singleton object O as instance of some anonymous class; it can be used to hold static members that are not associated with instances of some class.
  • object O extends T makes the object O an instance of trait T; you can then pass O anywhere, a T is expected.
  • if there is a class C, then object C is the companion object of class C; note that the companion object is not automatically an instance of C.

Also see Scala documentation for object and class.

object as host of static members

Most often, you need an object to hold methods and values/variables that shall be available without having to first instantiate an instance of some class. This use is closely related to static members in Java.

object A {
  def twice(i: Int): Int = 2*i
Enter fullscreen mode Exit fullscreen mode

You can then call the above method using A.twice(2).

If twice were a member of some class A, then you would need to make an instance first:

class A() {
  def twice(i: Int): Int = 2 * i

val a = new A()
Enter fullscreen mode Exit fullscreen mode

You can see how redundant this is, as twice does not require any instance-specific data.

object as a special named instance

You can also use the object itself as some special instance of a class or trait. When you do this, your object needs to extend some trait in order to become an instance of a subclass of it.

Consider the following code:

object A extends B with C {
Enter fullscreen mode Exit fullscreen mode

This declaration first declares an anonymous (inaccessible) class that extends both B and C, and instantiates a single instance of this class named A.

This means A can be passed to functions expecting objects of type B or C, or B with C.

Additional Features of object

There also exist some special features of objects in Scala. You can read the official documentation.

  • def apply(...) enables the usual method name-less syntax of A(...)
  • def unapply(...) allows to create custom pattern matching extractors
  • if accompanying a class of the same name, the object assumes a special role when resolving implicit parameters

Additional Answer:

A class is a definition, a description. It defines a type in terms of methods and composition of other types.

An object is a singleton — an instance of a class that is guaranteed to be unique. For every object in the code, an anonymous class is created, which inherits from whatever classes you declared object to implement. This class cannot be seen from Scala source code — though you can get at it through reflection.

There is a relationship between object and class. An object is said to be the companion-object of a class if they share the same name. When this happens, each has access to methods of private visibility in the other. These methods are not automatically imported, though. You either have to import them explicitly or prefix them with the class/object name.

For example:

class X {
  // class X can see private members of object X
  // Prefix to call
  def m(x: Int) = X.f(x)

  // Import and use
  import X._
  def n(x: Int) = f(x)

  private def o = 2

object X {
  private def f(x: Int) = x * x

  // object X can see private members of class X
  def g(x: X) = {
    import x._
    x.o * o // fully specified and imported
Enter fullscreen mode Exit fullscreen mode

2. What are all the uses of an underscore in Scala?


Existential types

def foo(l: List[Option[_]]) = ...
Enter fullscreen mode Exit fullscreen mode

Higher kinded type parameters

case class A[K[_],T](a: K[T])
Enter fullscreen mode Exit fullscreen mode

Ignored variables

val _ = 5
Enter fullscreen mode Exit fullscreen mode

Ignored parameters

List(1, 2, 3) foreach { _ => println("Hi") }
Enter fullscreen mode Exit fullscreen mode

Ignored names of self types

trait MySeq { _: Seq[_] => }
Enter fullscreen mode Exit fullscreen mode

Wildcard patterns

Some(5) match { case Some(_) => println("Yes") }
Enter fullscreen mode Exit fullscreen mode

Wildcard patterns in interpolations

"abc" match { case s"a$_c" => }
Enter fullscreen mode Exit fullscreen mode

Sequence wildcard in patterns

C(1, 2, 3) match { case C(vs @ _*) => vs.foreach(f(_)) }
Enter fullscreen mode Exit fullscreen mode

Wildcard imports

import java.util._
Enter fullscreen mode Exit fullscreen mode

Hiding imports

import java.util.{ArrayList => _, _}
Enter fullscreen mode Exit fullscreen mode

Joining letters to operators

def bang_!(x: Int) = 5
Enter fullscreen mode Exit fullscreen mode

Assignment operators

def foo_=(x: Int) { ... }
Enter fullscreen mode Exit fullscreen mode

Placeholder syntax

List(1, 2, 3) map (_ + 2)
Enter fullscreen mode Exit fullscreen mode

Method values

List(1, 2, 3) foreach println _
Enter fullscreen mode Exit fullscreen mode

Converting call-by-name parameters to functions

def toFunction(callByName: => Int): () => Int = callByName _
Enter fullscreen mode Exit fullscreen mode

Default initializer

var x: String = _   // unloved syntax may be eliminated
Enter fullscreen mode Exit fullscreen mode

Example showing why foo(_) and foo _ are different:

This example comes from 0__:

trait PlaceholderExample {
  def process[A](f: A => Unit)

  val set: Set[_ => Unit]

  set.foreach(process _) // Error 
  set.foreach(process(_)) // No Error
Enter fullscreen mode Exit fullscreen mode

In the first case, process _ represents a method; Scala takes the polymorphic method and attempts to make it monomorphic by filling in the type parameter, but realizes that there is no type that can be filled in for A that will give the type (_ => Unit) => ? (Existential _ is not a type).

In the second case, process(_) is a lambda; when writing a lambda with no explicit argument type, Scala infers the type from the argument that foreach expects, and _ => Unit is a type (whereas just plain _ isn’t), so it can be substituted and inferred.

Note that this example compiles in 2.13. Ignore it like it was assigned to underscore.

3. What is the difference between Scala’s case class and class?


Case classes can be seen as plain and immutable data-holding objects that should exclusively depend on their constructor arguments.

This functional concept allows us to

  • use a compact initialization syntax (Node(1, Leaf(2), None)))
  • decompose them using pattern matching
  • have equality comparisons implicitly defined

In combination with inheritance, case classes are used to mimic algebraic datatypes.

If an object performs stateful computations on the inside or exhibits other kinds of complex behavior, it should be an ordinary class.

Additional Answer:

Technically, there is no difference between a class and a case class — even if the compiler does optimize some stuff when using case classes. However, a case class is used to do away with a boiler plate for a specific pattern, which is implementing algebraic data types.

A very simple example of such types are trees. A binary tree, for instance, can be implemented like this:

sealed abstract class Tree
case class Node(left: Tree, right: Tree) extends Tree
case class Leaf[A](value: A) extends Tree
case object EmptyLeaf extends Tree
Enter fullscreen mode Exit fullscreen mode

That enable us to do the following:

// DSL-like assignment:
val treeA = Node(EmptyLeaf, Leaf(5))
val treeB = Node(Node(Leaf(2), Leaf(3)), Leaf(5))

// On Scala 2.8, modification through cloning:
val treeC = treeA.copy(left = treeB.left)

// Pretty printing:
println("Tree A: "+treeA)
println("Tree B: "+treeB)
println("Tree C: "+treeC)

// Comparison:
println("Tree A == Tree B: %s" format (treeA == treeB).toString)
println("Tree B == Tree C: %s" format (treeB == treeC).toString)

// Pattern matching:
treeA match {
  case Node(EmptyLeaf, right) => println("Can be reduced to "+right)
  case Node(left, EmptyLeaf) => println("Can be reduced to "+left)
  case _ => println(treeA+" cannot be reduced")

// Pattern matches can be safely done, because the compiler warns about
// non-exaustive matches:
def checkTree(t: Tree) = t match {
  case Node(EmptyLeaf, Node(left, right)) =>
  // case Node(EmptyLeaf, Leaf(el)) =>
  case Node(Node(left, right), EmptyLeaf) =>
  case Node(Leaf(el), EmptyLeaf) =>
  case Node(Node(l1, r1), Node(l2, r2)) =>
  case Node(Leaf(e1), Leaf(e2)) =>
  case Node(Node(left, right), Leaf(el)) =>
  case Node(Leaf(el), Node(left, right)) =>
  // case Node(EmptyLeaf, EmptyLeaf) =>
  case Leaf(el) =>
  case EmptyLeaf =>
Enter fullscreen mode Exit fullscreen mode

Note that trees construct and deconstruct (through pattern match) with the same syntax, which is also exactly how they are printed (minus spaces).

And they can also be used with hash maps or sets since they have a valid, stable hashCode.

4. What do all of Scala’s symbolic operators mean?


Let’s divide operators into four categories to make it easier to understand:

  • Keywords/reserved symbols
  • Automatically imported methods
  • Common methods
  • Syntactic sugars/composition

It is fortunate, then, that most categories are represented in the question:

->    // Automatically imported method
||=   // Syntactic sugar
++=   // Syntactic sugar/composition or common method
<=    // Common method
_._   // Typo, though it's probably based on Keyword/composition
::    // Common method
:+=   // Common method
Enter fullscreen mode Exit fullscreen mode

The exact meaning of most of these methods depends on the class that is defining them. For example, <= on Int means “less than or equal to”. The first one, ->, let’s look at the example below. :: is probably the method defined on List (though it could be the object of the same name), and :+= is probably the method defined on various Buffer classes.

So, let’s see them.

Keywords/reserved symbols

There are some symbols in Scala that are special. Two of them are considered proper keywords, while others are just “reserved”. They are:

// Keywords
<-  // Used on for-comprehensions, to separate pattern from generator
=>  // Used for function types, function literals and import renaming

// Reserved
( )        // Delimit expressions and parameters
[ ]        // Delimit type parameters
{ }        // Delimit blocks
.          // Method call and path separator
// /* */   // Comments
#          // Used in type notations
:          // Type ascription or context bounds
<: >: <%   // Upper, lower and view bounds
<? <!      // Start token for various XML elements
" """      // Strings
'          // Indicate symbols and characters
@          // Annotations and variable binding on pattern matching
`          // Denote constant or enable arbitrary identifiers
,          // Parameter separator
;          // Statement separator
_*         // vararg expansion
_          // Many different meanings
Enter fullscreen mode Exit fullscreen mode

These are all part of the language, and, as such, can be found in any text that properly describes the language, such as Scala Specification(PDF) itself.

The last one, the underscore, deserve a special description, because it is so widely used, and has so many different meanings. Here’s a sample:

import scala._    // Wild card -- all of Scala is imported
import scala.{ Predef => _, _ } // Exception, everything except Predef
def f[M[_]]       // Higher kinded type parameter
def f(m: M[_])    // Existential type
_ + _             // Anonymous function placeholder parameter
m _               // Eta expansion of method into method value
m(_)              // Partial function application
_ => 5            // Discarded parameter
case _ =>         // Wild card pattern -- matches anything
f(xs: _*)         // Sequence xs is passed as multiple parameters to f(ys: T*)
case Seq(xs @ _*) // Identifier xs is bound to the whole matched sequence
Enter fullscreen mode Exit fullscreen mode

Automatically imported methods

So, if you did not find the symbol you are looking for in the list above, then it must be a method or part of one. But, often, you’ll see some symbol, and the documentation for the class will not have that method. When this happens, either you are looking at a composition of one or more methods with something else, or the method has been imported into scope or is available through an imported implicit conversion.

These can still be found on ScalaDoc: you just have to know where to look for them. Or, failing that, look at the index (presently broken on 2.9.1, but available on nightly).

Every Scala code has three automatic imports:

// Not necessarily in this order
import      // _root_ denotes an absolute path
import _root_.scala._
import _root_.scala.Predef._
Enter fullscreen mode Exit fullscreen mode

The first two only make classes and singleton objects available. The third one contains all implicit conversions and imported methods since Predef is an object itself.

Looking inside Predef quickly show some symbols:

class <:<
class =:=
object <%<
object =:=
Enter fullscreen mode Exit fullscreen mode

Any other symbol will be made available through an implicit conversion. Just look at the methods tagged with implicit that receive, as a parameter, an object of a type that is receiving the method. For example:

"a" -> 1  // Look for an implicit from String, AnyRef, Any or type parameter
Enter fullscreen mode Exit fullscreen mode

In the above case, -> is defined in the class ArrowAssoc through the method any2ArrowAssoc that takes an object of type A, where A is an unbounded type parameter to the same method.

Common methods

So, many symbols are simply methods of a class. For instance, if you do

List(1, 2) ++ List(3, 4)
Enter fullscreen mode Exit fullscreen mode

You’ll find the method ++ right on the ScalaDoc for List. However, there’s one convention that you must be aware of when searching for methods. Methods ending in colon (:) bind to the right instead of the left. In other words, while the above method call is equivalent to:

List(1, 2).++(List(3, 4))
Enter fullscreen mode Exit fullscreen mode

Instead 1 :: List(2, 3), that would be equivalent to:

List(2, 3).::(1)
Enter fullscreen mode Exit fullscreen mode

So you need to look at the type found on the right when looking for methods ending in a colon. Consider, for instance:

1 +: List(2, 3) :+ 4
Enter fullscreen mode Exit fullscreen mode

The first method (+:) binds to the right and is found on List. The second method (:+) is just a normal method and binds to the left — again, on List.

Syntactic sugars/composition

So, here’s a few syntactic sugars that may hide a method:

class Example(arr: Array[Int] = Array.fill(5)(0)) {
  def apply(n: Int) = arr(n)
  def update(n: Int, v: Int) = arr(n) = v
  def a = arr(0); def a_=(v: Int) = arr(0) = v
  def b = arr(1); def b_=(v: Int) = arr(1) = v
  def c = arr(2); def c_=(v: Int) = arr(2) = v
  def d = arr(3); def d_=(v: Int) = arr(3) = v
  def e = arr(4); def e_=(v: Int) = arr(4) = v
  def +(v: Int) = new Example(arr map (_ + v))
  def unapply(n: Int) = if (arr.indices contains n) Some(arr(n)) else None

val Ex = new Example // or var for the last example
println(Ex(0))  // calls apply(0)
Ex(0) = 2       // calls update(0, 2)
Ex.b = 3        // calls b_=(3)
// This requires Ex to be a "val"
val Ex(c) = 2   // calls unapply(2) and assigns result to c
// This requires Ex to be a "var"
Ex += 1         // substituted for Ex = Ex + 1
Enter fullscreen mode Exit fullscreen mode

The last one is interesting because any symbolic method can be combined to form an assignment-like method that way.

And, of course, there are various combinations that can appear in code:

(_+_) // An expression, or parameter, that is an anonymous function with
      // two parameters, used exactly where the underscores appear, and
      // which calls the "+" method on the first parameter passing the
      // second parameter as argument.
Enter fullscreen mode Exit fullscreen mode

5. What is the difference between self-types and trait subclasses?


If you say B extends A, then B is an A. When you use self-types, B requires an A. There are two specific requirements that are created with self-types:

  • If B is extended, then you’re required to mix-in an A.
  • When a concrete class finally extends/mixes-in these traits, some class/trait must implement A.

Consider the following examples:

scala> trait User { def name: String }
defined trait User

scala> trait Tweeter {
     |   user: User =>
     |   def tweet(msg: String) = println(s"$name: $msg")
     | }
defined trait Tweeter

scala> trait Wrong extends Tweeter {
     |   def noCanDo = name
     | }
<console>:9: error: illegal inheritance;
 self-type Wrong does not conform to Tweeter's selftype Tweeter with User
       trait Wrong extends Tweeter {
<console>:10: error: not found: value name
         def noCanDo = name
Enter fullscreen mode Exit fullscreen mode

If Tweeter was a subclass of User, there would be no error. In the code above, we required a User whenever Tweeter is used, however, a User wasn’t provided to Wrong, so we got an error. Now, with the code above still in scope, consider:

scala> trait DummyUser extends User {
     |   override def name: String = "foo"
     | }
defined trait DummyUser

scala> trait Right extends Tweeter with User {
     |   val canDo = name
     | }
defined trait Right 

scala> trait RightAgain extends Tweeter with DummyUser {
     |   val canDo = name
     | }
defined trait RightAgain
Enter fullscreen mode Exit fullscreen mode

With Right, the requirement to mix-in a User is satisfied. However, the second requirement mentioned above is not satisfied: the burden of implementing User still remains for classes/traits which extend Right.

With RightAgain both requirements are satisfied. A User and implementation of User are provided.

For more practical use cases, please see the links at the start of this answer! But, hopefully, now you get it.

Additional Answer:

Self types allow you to define cyclical dependencies. For example, you can achieve this:

trait A { self: B => }
trait B { self: A => }
Enter fullscreen mode Exit fullscreen mode

Inheritance using extends does not allow that. Try:

trait A extends B
trait B extends A
error:  illegal cyclic reference involving trait A
Enter fullscreen mode Exit fullscreen mode

In the Odersky book, look at section 33.5 (Creating spreadsheet UI chapter) where it mentions:

In the spreadsheet example, class Model inherits from Evaluator and thus gains access to its evaluation method. To go the other way, class Evaluator defines its self type to be Model, like this:

package org.stairwaybook.scells
trait Evaluator { this: Model => ...
Enter fullscreen mode Exit fullscreen mode

6. What is the advantage of using abstract classes instead of traits?


There are two differences:

  • Abstract classes can have constructor parameters as well as type parameters. Traits can have only type parameters. There was some discussion that in future even traits can have constructor parameters.
  • Abstract classes are fully interoperable with Java. You can call them from Java code without any wrappers. Traits are fully interoperable only if they do not contain any implementation code.

Additional Answer:

Whenever you implement a reusable collection of behavior, you will have to decide whether you want to use a trait or an abstract class. There is no firm rule, but this section contains a few guidelines to consider.

If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all.

If it might be reused in multiple, unrelated classes, make it a trait. Only traits can be mixed into different parts of the class hierarchy.

If you want to inherit from it in Java code, use an abstract class. Since traits with code do not have a close Java analog, it tends to be awkward to inherit from a trait in a Java class. Inheriting from a Scala class, meanwhile, is exactly like inheriting from a Java class. As one exception, a Scala trait with only abstract members translates directly to a Java interface, so you should feel free to define such traits even if you expect Java code to inherit from it.

If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class. The issue is that when a trait gains or loses a member, any classes that inherit from it must be recompiled, even if they have not changed. If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine.

If efficiency is very important, lean towards using a class. Most Java runtimes make a virtual method invocation of a class member a faster operation than an interface method invocation. Traits get compiled to interfaces and therefore may pay a slight performance overhead. However, you should make this choice only if you know that the trait in question constitutes a performance bottleneck and have evidence that using a class instead actually solves the problem.

If you still do not know, after considering the above, then start by making it as a trait. You can always change it later, and in general, using a trait keeps more options open.

The other difference between classes and traits is that whereas in classes, super calls are statically bound, in traits, they are dynamically bound. If you write super.toString in a class, you know exactly which method implementation will be invoked. When you write the same thing in a trait, however, the method implementation to invoke for the super call is undefined when you define the trait.

There is also a subtle difference in the way abstract classes behave compared to traits. One of the linearization rules is that it preserves the inheritance hierarchy of the classes, which tends to push abstract classes later in the chain while traits can happily be mixed in. In certain circumstances, it’s actually preferable to be in the latter position of the class linearization, so abstract classes could be used for that. See constraining class linearization (mixin order) in Scala.

As of Scala 2.12, the trait’s binary compatibility behavior has changed. Prior to 2.12, adding or removing a member to the trait required recompilation of all classes that inherit the trait, even if the classes have not changed. This is due to the way traits were encoded in JVM.

As of Scala 2.12, traits compile to Java interfaces, so the requirement has relaxed a bit. If the trait does any of the following, its subclasses still require recompilation:

  • defining fields (val or var, but a constant is ok – final val without result type)
  • calling super
  • initializer statements in the body
  • extending a class
  • relying on linearization to find implementations in the right supertrait

But if the trait does not, you can now update it without breaking binary compatibility.

7. What is a TypeTag and how to use it?


A TypeTag solves the problem that Scala’s types are erased at runtime (type erasure). If we want to do

class Foo
class Bar extends Foo

def meth[A](xs: List[A]) = xs match {
  case _: List[String] => "list of strings"
  case _: List[Foo] => "list of foos"
Enter fullscreen mode Exit fullscreen mode

we will get warnings:

<console>:23: warning: non-variable type argument String in type pattern List[String]↩
is unchecked since it is eliminated by erasure
         case _: List[String] => "list of strings"
<console>:24: warning: non-variable type argument Foo in type pattern List[Foo]↩
is unchecked since it is eliminated by erasure
         case _: List[Foo] => "list of foos"
Enter fullscreen mode Exit fullscreen mode

To solve this problem, Manifests were introduced to Scala. But they have the problem not being able to represent a lot of useful types, like path-dependent-types:

scala> class Foo{class Bar}
defined class Foo

scala> def m(f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar]) = ev
warning: there were 2 deprecation warnings; re-run with -deprecation for details
m: (f: Foo)(b: f.Bar)(implicit ev: Manifest[f.Bar])Manifest[f.Bar]

scala> val f1 = new Foo;val b1 = new f1.Bar
f1: Foo = Foo@681e731c
b1: f1.Bar = Foo$Bar@271768ab

scala> val f2 = new Foo;val b2 = new f2.Bar
f2: Foo = Foo@3e50039c
b2: f2.Bar = Foo$Bar@771d16b9

scala> val ev1 = m(f1)(b1)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev1: Manifest[f1.Bar] = Foo@681e731c.type#Foo$Bar

scala> val ev2 = m(f2)(b2)
warning: there were 2 deprecation warnings; re-run with -deprecation for details
ev2: Manifest[f2.Bar] = Foo@3e50039c.type#Foo$Bar

scala> ev1 == ev2 // they should be different, thus the result is wrong
res28: Boolean = true
Enter fullscreen mode Exit fullscreen mode

Thus, they are replaced by TypeTags, which are both much simpler to use and well-integrated into the new Reflection API. With them we can solve the problem above about path-dependent-types elegantly:

scala> def m(f: Foo)(b: f.Bar)(implicit ev: TypeTag[f.Bar]) = ev
m: (f: Foo)(b: f.Bar)(implicit ev: reflect.runtime.universe.TypeTag[f.Bar])↩

scala> val ev1 = m(f1)(b1)
ev1: reflect.runtime.universe.TypeTag[f1.Bar] = TypeTag[f1.Bar]

scala> val ev2 = m(f2)(b2)
ev2: reflect.runtime.universe.TypeTag[f2.Bar] = TypeTag[f2.Bar]

scala> ev1 == ev2 // the result is correct, the type tags are different
res30: Boolean = false

scala> ev1.tpe =:= ev2.tpe // this result is correct, too
res31: Boolean = false
Enter fullscreen mode Exit fullscreen mode

They are also easy to use to check type parameters:

import scala.reflect.runtime.universe._

def meth[A : TypeTag](xs: List[A]) = typeOf[A] match {
  case t if t =:= typeOf[String] => "list of strings"
  case t if t <:< typeOf[Foo] => "list of foos"

scala> meth(List("string"))
res67: String = list of strings

scala> meth(List(new Bar))
res68: String = list of foos
Enter fullscreen mode Exit fullscreen mode

At this point, it is extremely important to understand to use =:= (type equality) and <:< (subtype relation) for equality checks. Do never use == or !=, unless you absolutely know what you do:

scala> typeOf[List[java.lang.String]] =:= typeOf[List[Predef.String]]
res71: Boolean = true

scala> typeOf[List[java.lang.String]] == typeOf[List[Predef.String]]
res72: Boolean = false
Enter fullscreen mode Exit fullscreen mode

The latter checks for structural equality, which often is not what should be done because it doesn’t care about things such as prefixes (like in the example).

A TypeTag is completely compiler-generated, that means that the compiler creates and fills in a TypeTag when one calls a method expecting such a TypeTag. There exist three different forms of tags:

ClassTag substitutes ClassManifest whereas TypeTag is more or less the replacement for Manifest.

The former allows to fully work with generic arrays:

scala> import scala.reflect._
import scala.reflect._

scala> def createArr[A](seq: A*) = Array[A](seq: _*)
<console>:22: error: No ClassTag available for A
       def createArr[A](seq: A*) = Array[A](seq: _*)

scala> def createArr[A : ClassTag](seq: A*) = Array[A](seq: _*)
createArr: [A](seq: A*)(implicit evidence$1: scala.reflect.ClassTag[A])Array[A]

scala> createArr(1,2,3)
res78: Array[Int] = Array(1, 2, 3)

scala> createArr("a","b","c")
res79: Array[String] = Array(a, b, c)
Enter fullscreen mode Exit fullscreen mode

ClassTag provides only the information needed to create types at runtime (which are type erased):

scala> classTag[Int]
res99: scala.reflect.ClassTag[Int] = ClassTag[int]

scala> classTag[Int].runtimeClass
res100: Class[_] = int

scala> classTag[Int].newArray(3)
res101: Array[Int] = Array(0, 0, 0)

scala> classTag[List[Int]]
res104: scala.reflect.ClassTag[List[Int]] =↩
        ClassTag[class scala.collection.immutable.List]
Enter fullscreen mode Exit fullscreen mode

As one can see above, they don’t care about type erasure, therefore if one wants “full” types TypeTag should be used:

scala> typeTag[List[Int]]
res105: reflect.runtime.universe.TypeTag[List[Int]] = TypeTag[scala.List[Int]]

scala> typeTag[List[Int]].tpe
res107: reflect.runtime.universe.Type = scala.List[Int]

scala> typeOf[List[Int]]
res108: reflect.runtime.universe.Type = scala.List[Int]

scala> res107 =:= res108
res109: Boolean = true
Enter fullscreen mode Exit fullscreen mode

As one can see, the method tpe of TypeTag results in a full Type, which is the same we get when typeOf is called. Of course, it is possible to use both, ClassTag and TypeTag:

scala> def m[A : ClassTag : TypeTag] = (classTag[A], typeTag[A])
m: [A](implicit evidence$1: scala.reflect.ClassTag[A],↩
       implicit evidence$2: reflect.runtime.universe.TypeTag[A])↩
      (scala.reflect.ClassTag[A], reflect.runtime.universe.TypeTag[A])

scala> m[List[Int]]
res36: (scala.reflect.ClassTag[List[Int]],↩
        reflect.runtime.universe.TypeTag[List[Int]]) =↩
Enter fullscreen mode Exit fullscreen mode

The remaining question now is what is the sense of WeakTypeTag ? In short, TypeTag represents a concrete type (this means it only allows fully instantiated types) whereas WeakTypeTag just allows any type. Most of the time one does not care which is what (which means TypeTag should be used), but for example, when macros are used which should work with generic types they are needed:

object Macro {
  import language.experimental.macros
  import scala.reflect.macros.Context

  def anymacro[A](expr: A): String = macro __anymacro[A]

  def __anymacro[A : c.WeakTypeTag](c: Context)(expr: c.Expr[A]): c.Expr[A] = {
    // to get a Type for A the c.WeakTypeTag context bound must be added
    val aType = implicitly[c.WeakTypeTag[A]].tpe
Enter fullscreen mode Exit fullscreen mode

If one replaces WeakTypeTag with TypeTag an error is thrown:

<console>:17: error: macro implementation has wrong shape:
 required: (c: scala.reflect.macros.Context)(expr: c.Expr[A]): c.Expr[String]
 found   : (c: scala.reflect.macros.Context)(expr: c.Expr[A])(implicit evidence$1: c.TypeTag[A]): c.Expr[A]
macro implementations cannot have implicit parameters other than WeakTypeTag evidences
             def anymacro[A](expr: A): String = macro __anymacro[A]
Enter fullscreen mode Exit fullscreen mode

The official documentation site of Scala also contains a guide for Reflection.

8. Is there any difference between ::: and ++ for concatenating lists in Scala?


The list was originally defined to be functional-languages-looking:

1 :: 2 :: Nil // a list
list1 ::: list2  // concatenation of two lists

list match {
  case head :: tail => "non-empty"
  case Nil          => "empty"
Enter fullscreen mode Exit fullscreen mode

Of course, Scala evolved other collections, in an ad-hoc manner. When 2.8 came out, the collections were redesigned for maximum code reuse and consistent API, so that you can use ++ to concatenate any two collections — and even iterators. List, however, got to keep its original operators, aside from one or two which got deprecated.

Additional Answer:

Always use :::. There are two reasons: efficiency and type safety.


x ::: y ::: z is faster than x ++ y ++ z, because ::: is right associative. x ::: y ::: z is parsed as x ::: (y ::: z), which is algorithmically faster than (x ::: y) ::: z (the latter requires O(|x|) more steps).

Type safety

With ::: you can only concatenate two Lists. With ++ you can append any collection to List, which is terrible:

scala> List(1, 2, 3) ++ "ab"
res0: List[AnyVal] = List(1, 2, 3, a, b)
Enter fullscreen mode Exit fullscreen mode

++ is also easy to mix up with +:

scala> List(1, 2, 3) + "ab"
res1: String = List(1, 2, 3)ab
Enter fullscreen mode Exit fullscreen mode

9. What is a sealed trait?


A sealed trait can be extended only in the same file as its declaration.

They are often used to provide an alternative to enums. Since they can be only extended in a single file, the compiler knows every possible subtype and can reason about it.

For instance with the declaration:

sealed trait Answer
case object Yes extends Answer
case object No extends Answer
Enter fullscreen mode Exit fullscreen mode

The compiler will emit a warning if a match is not exhaustive:

scala> val x: Answer = Yes
x: Answer = Yes

scala> x match {
     |   case No => println("No")
     | }
<console>:12: warning: match is not exhaustive!
missing combination            Yes
Enter fullscreen mode Exit fullscreen mode

So you should use sealed traits (or sealed abstract class) if the number of possible subtypes is finite and known in advance. For more examples, you can have a look at list and option implementations.

Additional Answer:

A sealed trait is the same as a sealed class?

As far as sealed goes, yes. They share the normal differences between trait and class, of course.

Or, if not, what are the differences ?

When is it a good idea to use a sealed trait (and when not) ?

If you have a sealed class X, then you have to check for X as well as any subclasses. The same is not true of sealed abstract class X or sealed trait X. So you could do sealed abstract class X, but that’s way more verbose than just trait and for little advantage.

The main advantage of using an abstract class over a trait is that it can receive parameters. That advantage is particularly relevant when using type classes. Let’s say you want to build a sorted tree, for instance. You can write this:

sealed abstract class Tree[T : Ordering]
Enter fullscreen mode Exit fullscreen mode

but you cannot do this:

sealed trait Tree[T : Ordering]
Enter fullscreen mode Exit fullscreen mode

since context bounds (and view bounds) are implemented with implicit parameters. Given that traits can’t receive parameters, you can’t do that.

You can prefer sealed trait and use it unless some particular reason makes you use a sealed abstract class. But in-your-face reasons you cannot ignore, such as using type classes.

10. How to use java.String.format in Scala?


Here’s a Scala example:

val placeholder = "Hello %s, isn't %s cool?"
val formatted = placeholder.format("Ivan", "Scala")
Enter fullscreen mode Exit fullscreen mode

Alternative Answer:

You don’t need to use numbers to indicate positioning. By default, the position of the argument is simply the order in which it appears in the string.

Here’s an example of the proper way to use this:

String result = String.format("The format method is %s!", "great");
// result now equals  "The format method is great!".
Enter fullscreen mode Exit fullscreen mode

You will always use a % followed by some other characters to let the method know how it should display the string. %s is probably the most common, and it just means that the argument should be treated as a string.

Here are a few examples just to give you an idea:

// we can specify the # of decimals we want to show for a floating point:
String result = String.format("10 / 3 = %.2f", 10.0 / 3.0);
// result now equals  "10 / 3 = 3.33"

// we can add commas to long numbers:
result = String.format("Today we processed %,d transactions.", 1000000);
// result now equals  "Today we processed 1,000,000 transactions."
Enter fullscreen mode Exit fullscreen mode

String.format just uses a java.util.Formatter, so for a full description of the options, you can see the Formatter javadocs.

And, you will see in the documentation that it is possible to change the default argument ordering if you need to. However, probably the only time you’d need/want to do this is if you are using the same argument more than once.

In Conclusion

These are the 10 most commonly asked questions about Scala. If you have any suggestions or any confusion, please comment below. If you need any help, we will be glad to help you.

We, at Truemark, provide services like web and mobile app development, digital marketing, and website development. So, if you need any help and want to work with us, please feel free to contact us.

Hope this article helped you.

This article was first published on DevPostbyTruemark.

Top comments (1)

satyam_prg profile image
Satyam Jaiswal

Thanks for Sharing. Practice here the top Interview Questions on Scala.