Avoid These Mistakes While Coding in Scala

Yusuf Kasım Temel
Insider Engineering
8 min readMar 20, 2023

--

It is no surprise that programming can be challenging for most people at some point. For me, the challenge was to learn functional programming in the first place because it required a completely different approach from OOP. But in time, I embraced this approach as it forced me to write really clean, readable, easy-to-debug code. On the other hand, it did not provide the organizational ability which OOP offered.

Then I met Scala, a functional object-oriented programming language. Scala had everything I wanted and after spending some time with it, I fell in love. But it had one problem: its cross-paradigm structure did not force me to write functionally. In other words, Scala leads developers to write inappropriate codes.

In two and a half years of my Scala development experience, I faced some difficulties due to several reasons and witnessed many people making mistakes. So, I am going to cover some common mistakes I encountered as well as I myself made in this blog post to help new Scala developers save their time.

Before I go further, I assume that you spent some time with Scala and are familiar with the main concepts.

Not Only Val but Also Immutable

In Scala, val and var are used to indicate the immutability of the data. However, we can find mutable val types that make it possible to change data within it.

A mutable set using val

We need to understand that, we should not change any object in the Scala program. Instead of that, we can create new objects with changed data and use it.

Collections are a different case to focus on when immutable data manipulation comes into the table. You need to be familiar with pattern matching and linked lists for this specific situation. All functional programming languages (as far as I know) have a pattern matching and special linked list manipulation method.

As you can see above, we recursively created our list and then reversed it to save it from the computation. Of course, Scala has built-in functions like map, fold, etc. for collection manipulation purposes. However, not knowing how to create a list becomes a problem when complexity increases.

Side Effect and Unit

Functional programming wants us to cause as few side effects as possible. So, it assumes that we are creating a function for its return value.

Of course, some functions will cause side effects and some functions will be created for only side-effect purposes. In those situations, do not use Unit as the return type. It makes exception handling harder. In addition to that, if you map a Future[Unit], you can not force the future-for structure to wait for the unit function to finish before starting others.

Instead of Unit, you can use Success, Failure objects you created.

Short Functions

When I first used a pure-functional programming language, it was impossible to write long functions because of the looping structure. You need to write new recursive functions when you need loops. After some time, I realized all my functions were short and layered.

This kind of organization makes you read and understand the code effortlessly. This layered structure looks like a tree (data structure) and decreases your brain’s CPU consumption massively.

A tree of functions

On the other hand, short functions force you to find new names for functions. And when you try to give a name to a function, you are indicating what the function does. With this kind of approach, you can easily apply the single responsibility principle for the functions.

Additionally, you can easily see highly connected functions and create a new file for them. This makes your functions reusable and not forgettable.

Containers

Containers are the wrappers that hold data. We use them to manipulate data spending less effort. However, it is important to know or check the unique techniques for all the individual containers before working with them.

Built-in Functions

In functional programming, there are lots of built-in methods for you to manipulate containers. You need to look at the methods of the container, before manipulating it. If you do not, you may write unnecessary codes to do the same thing.

// A simple manipulation mistake
val option = Some("foo")

val newOption = {
if (option.isDefined) option.get + "bar"
else None
}
// An example of better usage
val option = Some("foo")

val newOption = option.map(_ + "bar")

Map, flatmap, flatten, filter, foreach, fold, and collect are some of these built-in functions. You need to know and use these in the necessary places.

Always Use getOrElse

Some containers have get and getOrElse methods. People sometimes use the get method because they are pretty sure that the container has value. However, the next developer may empty the container for another task before that usage and it may turn out to an incident. So, you should write the code thinking of other developers’ common behaviors.

Do Not Name Values With Their Container Types

Naming values with their container types makes code harder to read. IDEs make it so easy to see types. When you need to see the type of value, you can use IDEs appropriate approach.

However, most of the time we do not need the container type. We are only interested in what data they carry to understand the code. When it comes to manipulating the data, yes you need the type. But, you are going to look at the type with or without it. Using container type in naming almost never makes it faster to write the code.

A hard to read code.
An easy-to-read code.

Futures

Futures were hard for me at the beginning. After some time, it became much and much easier to use.

Exception Handling

I saw that people do not use its built-in methods. One of them is recover and recoverWith. I never used try-and-catch in Scala. When I need it, I use the built-in Try container. And futures use Try containers in themselves. So, when the future returns a Failure it automatically runs the code in the recover part.

// Future exception handling 
val future = Future(throw new IndexOutOfBoundsException)
val k = future
.recoverWith{
case _: IndexOutOfBoundsException => Future(6)
case _ => Future(5)
}

println("Future is " + Await.result(k, 100 seconds))
// Future is 6

Future.Sequence

Future.sequence takes a sequence of futures and returns one future that contains a sequence of values. I saw people manipulating data for each future.

val futures = (1 to 10).map(Future(_))

// Wrong
futures.map(future => future.map(_ * 2))

// Right
Future.sequence(futures).map(seq => seq.map(_ * 2))

This is mostly happening when we need multiple data from a service. We can easily change that futures container into one future container.

Of course, you need some info before using this. If one of the futures returns failure, the returning future fails with the first exception returning the index’s exception in the sequence. It always waits for all the futures to finish, before throwing the exception.

val future1 = Future{
Thread.sleep(3000)
throw new IndexOutOfBoundsException
}

val future2 = Future{
Thread.sleep(4000)
println("Normal")
5
}

val result = Future.sequence(Seq(future2, future1))
.recover{case e =>
e
}

println(Await.result(result, 200000 seconds))

//Normal
//java.lang.IndexOutOfBoundsException

For-Yield

For-yield is an inseparable part of the future. If you want to handle more than one future, you should use this structure. If you do not use this structure, it is likely that your code will be harder to read.

// Hard to read

future1.flatMap{
num => future2(num).flatMap{
num => future3(num)
}
}

// Easy to read
for {
value1 <- future1
value2 <- future2(value1)
value3 <- future3(value2)
} yield value3

If you want to run some futures in parallel you need to execute them out of the for-yield structure. Otherwise, it will execute them one by one.

F-Bounded Polymorphism

Requirements: Type Classes and OOP

The biggest mistake I encountered is this about polymorphism in Scala. You should not write Scala in an OOP design if you do not know this concept.

In Scala, you can not change data inside an object. That is why you need to always create a copy object with the changed attribute you desired. However, you need to know the type of the object you want to create, to be able to create it. But, you can not know the type of the object, if you take the trait as a type.

// A mutable approach to polymorphism
trait Animal{
var name: String
}

case class Cat(var name:String, var color: String) extends Animal

case class Dog(var name:String, var age: Int) extends Animal

def changeName(animal: Animal): Animal = {
animal.name = "boo"
animal
}

val cat = Cat("foo", "blue")
println(changeName(cat))
An immutable approach try to polymorphism

We created the new object however, we lost the type. From now on, it is just an animal for us. We can not get its colour anymore unless finding it’s type again

// The mutable approach to polymorphism
trait Animal[T]{
val name: String

def copyWithNewName(newName: String): T
}

case class Cat(name:String, color: String) extends Animal[Cat]{
override def copyWithNewName(newName: String): Cat = this.copy(name = newName)
}

case class Dog(name:String, age: Int) extends Animal[Dog]{
override def copyWithNewName(newName: String): Dog = this.copy(name = newName)
}

def changeName[T <: Animal[T]](animal: T): T = {
val newAnimal = animal.copyWithNewName("boo")
newAnimal
}

val cat = Cat("foo", "blue")
changeName(cat).color

Now we can create a new cat inside the method and use it outside of the method as a cat because we did not lose its type inside of the method.

This is the way to implement polymorphism in an immutable code design. It may look a little complex, but it is pretty simple when you become more comfortable using Scala.

No More Than One Structure In A File

I saw so many people use more than one structure (Class, Object, Trait) in a file (Unless companion objects.). When you do it, IDE can not show the type of structure in the projects tab. Additionally, it is hard to understand multiple structures in one file.

Conclusion

Every developer has to go through certain stages before they become competent in a programming language but it is really important to avoid these mistakes before starting to code Scala. It becomes more and more complex without these functional approaches in time. After some time, it becomes impossible to refactor the code and you find yourself stuck with what you have.

I hope this post helps you avoid the mistakes I faced before. Thanks for your time.

I hope this post helps you avoid the mistakes we all faced before. If you have any questions, feel free to contact me on LinkedIn or comment below. If you liked this article, you might also like Spray-json in Scala for Case Class Serialization by Jamil Najafov. Also do not forget to check our medium page.

--

--