Scala Job Interview – Language Questions

serious black student solving math equation on blackboard

This is the second installment of this series. The first post in the series gives you a bit more context. Anyway, this is my try to answer Scala Interview grade Questions I found in a blog post by Signify. You can find alternate answers here.

Language Questions

What is the difference between a var, a val and def?

All these keywords are for declaring something. var declares a variable, val declares a variable that cannot be re-assigned and def declares a function.

scala
var a = 3
val b = 3
def c = 3

In the above code, a is a variable as you can find in almost every language, you can increment it, re-assign it. Standard stuff.

b is a variable bound to value 3 forever? Attempting to change its value results in a compile-time error. c is a parameterless function that returns 3. Interestingly a, b and c evaluate to the integer 3. Also note that val just prevents reassignment, not value change. E.g. in the following code:

scala
class Foo {
  private var a: Int = 3
  def change( newA : Int ) : Unit = { a = newA }
}

val x = new Foo()
x.change(8)

There is no compile-time error because you are not changing the bound between x and the instance that references. You are just changing the instance.

As for val you can’t re-define a symbol declared with def.

Note though that when evaluating c a function is always called (maybe this is optimized aways, but the semantic is a function call.

The last consideration is that since functions are first-class citizens in Scala, sometimes things are not well separated as they are in other languages:

val f = ( x: Int ) => 2*x

f is a variable that is bound to a function accepting an integer and returning its double. The difference with

def f( x: Int ) = 2*x

is subtle – in the first case, you are using a variable with its space, while in the second case you have just a function with a symbolic name.

What is the difference between a trait and an abstract class?

A trait defines a specific and partial nature of an object. Both a house and a car have doors, that can be opened/closed/entered/exited, but car and house have no common ancestor from the OO point of view.

In Scala, traits can have code and variables (but not a constructor). This sets Scala traits apart from Java Interfaces that can’t have code or variables. Note that variables in trait, when not initialized, are considered part of the interface that needs to be implemented by classes that extend such trait.

An abstract class specifies the nature of an object. Classes that extend the abstract class are in an “is-a” relationship with the abstract class. The abstract class contains at least one method with no implementation (this method is abstract and needs to be implemented by the extending class). The abstract class may have a constructor and fields. As for the trait, an uninitialized variable is considered abstract and the implementation is needed in the extending class.

I read that a Scala trait is usable from Java code as an interface, only if the trait obeys the Java interface restrictions (no code, no variables).

What is the difference between an object and a class?

The object construct is a handy and interesting concept. Basically, an object is syntactic sugar for the Singleton pattern. Rather than implementing your getInstance() method, the object automatically implements and invokes the getInstance() each time you access a member (variable or function) of the class.

Because of this you never explicitly instantiate an object. On the other hand, a class may have many instances you need to explicitly instantiate via the new operator.

Since in Scala there are no static members, the object is a convenient place where to store them. See the answer to “companion object

What is a case class?

A case class is syntactic sugar over a standard Scala class that allows the programmer to easily write (and read) classes whose instances are constant. Such classes can be used in pattern matching without the need for extra coding. Notably for a case class

  • getters are automatically produced for constructor arguments (as for val specifiers in standard class constructor);
  • unapply method is automatically defined for use of case class instances in pattern matching;
  • there is no need for the “new” operator when creating a new instance (it is like a companion object exists with an apply method matching the case class constructors that instantiates the class).
  • equals, copy, toString and hashCode methods are automatically generated.

Translating these into code, I’ll try to implement a case class with two arguments, using the class construct.

object CaseClass {
  def apply( a: Int, b: String ) : CaseClass = new CaseClass( a, b )
  def unapply( x: CaseClass ) : Some[(Int,String)] = Some(( x.a, x.b ))
}

class CaseClass( val a: Int, val b: String ) {
  def equals( other: CaseClass ) : Boolean = a == other.a &&  b == other.b
  def copy( a: Int = this.a, b: String = this.b ) : CaseClass = new CaseClass( a, b )
  def toString : String = s"CaseClass($a,$b)"
  def hashCode : Int = scala.runtime.ScalaRunTime._hashCode( this )
}

I had to look up how copy (dumb I am, I could figure it out myself) and hashCode are implemented. On StackOverflow, I found this detailed description of how ScalaRunTime._hashCode is implemented.

What is the difference between a Java future and a Scala future?

To be honest (and you should be during a job interview) I don’t know. I know that Scala Futures have the monad interface (Unit, flatMap) that makes them composable. This is a great advantage since lets the programmer focus on the actions running concurrently rather than the concurrency management.

So I looked up Java (8) Futures and found that they have no composition method, also the get method blocks the caller until the data is ready and throws an exception if the concurrent execution failed. Java Future get comes in two flavors with and without the timeout. Waiting on a result without a timeout is usually a bad idea, but is the most tempting for the lazy programmer.

Also Java Future is an interface, while Scala Future is a class. Scala Future is executed within an ExecutionContext provided via implicit argument, while Java Future has to be explicitly given to an Executor for execution.

Scala Future is much more refined and safer than the Java equivalent. Composition and handling of the failures set Scala Future apart in a superior class.

What is the difference between unapply and apply, when would you use them?

Until a little while ago I knew unapply only by name. Thanks to this list (so thanks to Signify once more), now I have a pretty good understanding (also thanks to this very good post on unapply)

So, what is a constructor? A constructor takes a tuple (constructor arguments) and builds an object. This is very clear if we write the companion object apply method, or we use a case class –

class MyPrecious( val power: Int, val name: String )

object MyPrecious {
  def apply( val power: Int, val name: String ) : MyPrecious = new MyPrecious( power, name )
}

Unapply allows you to do the reverse, i.e. turning an object in a tuple containing the values of constructor arguments –

val p = MyPrecious( 10, "the One" )
val MyPrecious( power, name ) = p

As long as the class state can be fully identified by constructor parameters, you can switch back and forth using apply and unapply. Unapply comes really handy for pattern matching, allowing the programmer to express matching conditions and retrieval in the parameter of constructors.

You can write the unapply function for every class in the following way:

object MyPrecious {
  def apply( val power: Int, val name: String ) : MyPrecious = new MyPrecious( power, name )
  def unapply( p: MyPrecious ) : Option[(Int,String)] = Some( (p.power, p.name ))
}

Note that unapply returns an Option. This is needed for matching when we want to specialize or classify objects from a class.

What is a companion object?

Easy-peasy, an object is a singleton dressed in syntactic sugar (see the answer to “what’s the difference between class and object?“). A companion object is an object that has the same name as a class. There is nothing special in the language about the relationship between these two entities, but conventionally you use the companion object for everything you would declare static in Java (or other languages that supports static class members).

Since there is no built-in relationship you still need to import symbols from the object namespace.

object Foo {
  val Size : Int = 3
  private val Height : Int = 2
  
  def frobber( f: Foo ): Int = f.baz
}

class Foo {
  import Foo.Size
  import Foo.Height
  
  def dummy( n: Int ) = Size*n
  def bar( n: Int ) = Height*n
  
  private val baz : Int = 42
}

Well, the part about no built-in relationship is not entirely true: class and companion object may access private members of one another. This is usually handy, but sort of bending the rules for this special case.

What is the difference between the following terms and types in Scala: Nil, Null, None, Nothing?

Nil is the empty list, Null is the null reference and None is the invalid value for Option[T].

What is Unit?

For us, C and C++ programmers, Unit is void in Scala lingo. In more accurate terms, we can say that Unit is a type without values. In the Scala type hierarchy stay among its siblings such as Int, Char, Boolean, and so on.

The purpose of Unit is to let the programmer define functions that return no value. Such functions are usually called procedures and are seen as something that’s looked at with suspicion in functional programming. In fact, the only purpose of a function that returns no value is to cause side effects.

What is the difference between a call-by-value and call-by-name parameter?

A call-by-value parameter is just a plain parameter, the value of this parameter is evaluated at calling place:

def g( name: String ) : Int = {
  println( "side-effect" )
  name.length
}

def f( a: Int, b: Boolean ) : Int = {
  if( b ) {
    2*a
  }
  else {
    0
  }
}

// ...

f( g( "foo" ), false )

Function f uses its argument a only when argument b is true. When f is called the evaluation of all its arguments occurs at the calling place, therefore function g is called in order to get its return value.

Call-by-name parameter moves the evaluation of the parameter at the callee side:

def g( name: String ) : Int = {
  println( "side-effect" )
  name.length
}

def f( a: => Int, b: Boolean ) : Int = {
  if( b ) {
    2*a
  }
  else {
    0
  }
}

// ...

f( g( "foo" ), false )

Now g is called only when b is true, otherwise, it is not called.

This is handy in some specific cases, the first that comes to mind is logging. There is a logging level and logs are recorded only when the current logging level is lower than the level of the log call. If the data to log needs a significant amount of CPU to be computed or the log is inside a critical loop, then you may want to use the call-by-name convention in order to avoid computations whose result will be ignored.

Even if the log level can be known outside the logging function, it would be annoying to wrap log calls in an if statement to avoid the call at all.

If you look carefully at the notation used for the call-by-name you will recognize that the type of the argument is a function that accepts no argument and returns the type expected for the argument. And that’s it, Scala automatically converts the expression at the call site in a lambda function that will be called at the callee site when the parameter is referenced. From this point of view, the language ignores the concept of call-by-value/call-by-name – it is always the same calling mechanism and the automatic promotions the language applies for converting the expression to the needed type do the rest.

To be honest once again 🙂 I knew the mechanism (and I have used it a couple of times myself) but forgot the name and I erroneously thought that this was about calling a function with named arguments.

How does Scala’s Stream trait levarages call-by-name?

I hadn’t the palest idea the Scala Stream existed 🙂 but a bit of web lookup cleared the matter. In my defense, I have to report that Stream is deprecated in favor of LazyList. So, I’ll talk about LazyList (but concepts applies also to the deprecated Stream)

Scala LazyList is a … lazy List, that is a list whose items are evaluated only when they are referenced. You build a Stream using the #:: operator (by comparison, you use the :: operator to build a list). Consider the following code:

def f( s: String ) : Int = {
  println( "f evaluated" )
  s.length
}

val s = 1 #:: 2 #:: f("foo") #:: LazyList.empty

s.foreach( println )

If you run it, it will print:

1
2
f evaluated
3

That proves (at least in my intentions) that f is not evaluated when the list is built, but when the list is evaluated in the foreach statement. This can be achieved in your code as well by using call-by-name and lazy modifier, as in the following example (I guess it could be written with less code) –

class MyLazyList

object MyLazyListItem {
  def apply[A]( value: => A, next: MyLazyList  ) : MyLazyListItem[A] = 
    new MyLazyListItem( value, next )
  def unapply[A]( item: MyLazyListItem[A] ) : Option[(A,MyLazyList)] =
    Some( item.lazyValue, item.next )
}

class MyLazyListItem[A]( value: => A, val next: MyLazyList ) extends MyLazyList {
  lazy val lazyValue = value
}
case object MyLazyListEmpty extends MyLazyList

@tailrec
def printer(list : MyLazyList) : Unit = {
  list match {
    case MyLazyListItem( value, next ) =>
      println( value )
      printer( next )
    case MyLazyListEmpty =>
  }
}

val x: MyLazyList = MyLazyListItem( 1, MyLazyListItem(2, MyLazyListItem( f("foo"), MyLazyListEmpty )))
println( "x built" )
printer( x )

Define uses for the Option monad and good practices it provides.

I wrote a lengthy blog post about Option misuses and good uses. Briefly, Option has to be used for optionality and not for errors, since Option has no way to describe what error occurred.

Usage of Option with map and flatMap methods reduces the need for conditional expressions making code easier to read and understand. These are the basis for a safer and more correct code.

How does yield work?

The yield keyword is used with for loops. In functional programming, everything returns a value (i.e. everything is a function). So take the if instruction – it is pretty easy to turn it into a function – returned value is the last evaluated on the branch selected by the conditional expression (this is not dissimilar from the ternary operator in C-like languages). Match instructions can be functionalized in the same way. But for eludes a bit this schema, since it scans a sequence but it is not evident what the loop may produce.

Thinking about sequences, a natural application in functional programming is map and flatMap functions. Therefore the idea used in Scala is – make for behaving like a map/flatMap on steroids. More specifically for the question, the yield defines the transformation to apply to each item to produce the new sequence –

val list = List( 1, 2, 3 )
val doubles = for( x <- list ) yield( 2*x )

doubles contains List( 2, 4, 6 ).

Since for uses map and flatMap, it can be applied to every kind of monads, from List to Option, also you may add conditions to filter out values you don’t want in the sequence. This makes for a very powerful construct that, if used properly, enables the programmer to write more concise, terse, and understandable code.

Compare:

for {
  db <- dbConnect( dbConnectionParams )
  company <- getCompany( db, companyName )
  user <- getUsersByCompany( company ) if user.role == Roles.ProjectManager
}
yield {
  user.salary
}

With

dbConnect( dbConnectionParams ).
  flatMap( db => getCompany( db, companyName ) ).
  flatMap( company => getUsersByCompany( company )).
  flatMap( user -> if( user.role == Roles.ProjectManager ) Some(user) else None ).
  filter( _.isDefined ).
  map( _.get.salary )

Explain the implicit parameter precedence.

This is another question I would reply to with a blank stare (at least for a few seconds before entering survival mode and cranking out some vague answer). Scala used implicits for two different purposes. First to provide extension methods to an existing class via implicit conversion. Second to pass implicit parameters to a function, i.e. you can omit typing extra parameters because the language takes care of passing what is needed.

I wrote “used” because Scala 3 got rid of implicits and introduced new ways of doing the same things. Now, in general, things happening under the hood without an explicit representation in the source code are both good and bad. Good because, if done well, they allow the programmer to focus on a higher level of abstraction (that’s the whole point of high-level programming languages). And Bad because they could hide critical operations that could go unnoticed.

I’m not really fond of the implicit parameter construct, but sometimes it offers a clean way to provide the needed information saving the extra wording. Consider the ExecutionContext, which is basically a thread pool, and the Future constructs. When you build a Future it takes implicitly an ExecutionContext. In this way you correctly let the programmer decide which ExecutionContext use for each Future instance, but Scala gets the fuss out of sight since a) everyone knows that a Future needs an ExecutionContext, b) usually it is just a mechanism for the internal working of the future, c) can be defined just once for all the Future instances in a module.

So, of course, there should be a way, for the compiler to pick one among a group of equally fitting implicit values. I found a long (and clear) dissertation on implicit precedence by Eugene Yokota, that boils down to two categories, where implicits are looked up

  1. A local declaration in the current invocation scope, an import, declarations in outer scope, inheritange and package object accessible without prefix;
  2. Symbols from implicit scope, that is the scopes associated to the implicit type we need (e.g. the package object or the companion object of the type and so on).

I wouldn’t go too much in detail because the aforementioned blog post is much better than what I could write. Also, I think that relying too heavily on implicits is usually a bad thing and that if you ever stumble in a case where implicit resolution precedence is causing confusion, you should change the code that moves more data transfer in the explicit realm.

Also, I see not much gain in investing in a technique that is no longer used in the current version of the language.

What operations is a for comprehension syntactic sugar for?

For comprehension is syntactic sugar for a sequence of flatMap, possibly intermixed with filter, eventually terminated by a map. The only limitation is that you can operate only on the same type of monad. E.g. you can’t intermix a for comprehension of Option and Either. You can do it, only if you convert one monad into another to have all them of the same type.

You can read the answer to “how does yield work” to get more information on the for comprehension.

Streams:

What consideration you need to have when you use Scala’s Streams?

The first consideration is that they are deprecated in favor of LazyList. As anything lazy you must ensure that the evaluation of the content can be performed when needed. This usually means that you need to change the state in the objects involved (or the change of state still allows the computation to be performed – even if I consider this a bad idea).

What technique does the Scala’s Streams use internally?

Scala streams are enabled by two techniques – call-by-name and lazy val. The first allows passing parameters that will be evaluated by the callee instead of the caller. Lazy val are variables that are not evaluated until the first time they are accessed.

Conclusions

Counting answers I knew against the total number of questions, I made 10/17. Not something I’m so proud of. In my defense, I call the deprecation of Scala Streams (3 questions out of 17). Without those, I would have totalized 10/14 a much better result.

Again I had a good time finding and presenting answers. I found some questions better than others because they allowed me to write more and better present my experience and my point of view. You can easily recognize them – they are those with longer answers.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.