It’s actually my second time taking a crack on Scala, back then I kind of give up half-way because the way of thinking seem so different from usual Java and I can’t figure out how Scala is supposed to be written, moreover I learn ZIO immediately at the beginning and it really confused me.

Now, about 6 month later, I think I start to understand it.

Functional Language

My first key of understanding this whole language start weirdly on another language: Elixir and Gleam.

Elixir and Gleam is like brother I guess, I won’t talk too much about them (that is TWO different article to be written) but one thing that is common from both of them is that both are Functional Language.

Functional Language, unlike the famous OOP (Object Oriented Programming) is a different paradigm of thinking. On Functional Language we are driven to write with tenet of:

Focus on What happen, not How it be done

This is very confusing, and I can even make mistake when explaining this, so bear with me.

An example case:

we had a list of employee, get everyone on IT department and over 35 year old.

This is how we would do this with OOP, (I’m going to use Java here, since Scala and Java is like cousin and both run on JVM, so I hope I can highlight how cousin Java do things differently)

public List<Employee> getEmployeeOnITAndOver35() {
	List<Employee> allEmployee = getAllEmployee() // a function to get all employee
	List<Employee> ITEmployeeOver35 = new ArrayList();
	for(Employee oneEmployee : allEmployee) {
		if(oneEmployee.getDepartment().equals("IT")) {
			if(oneEmployee.getAge() > 35 ) {
				// ... Yes this is what we want, collect this employee and return it
				ITEmployeeOver35.add(oneEmployee);
			}
		}
	}
	return ITEmployeeOver35;
}

As you can see, this is how OOP do this, we had the original allEmployee object and then do a loop with oneEmployee object and keep the filtered data on second object ITEmployeeOver35. We use 3 object for this simple job.

Now, lets see how Scala as Functional Language will do this.

def getEmployeeOnITAndOver35(): List[Employee] = {
	getAllEmployee()
	.filter(e => e.getDepartment == "IT" && e.getAge > 35)
}

First thing you notice is how short this is, but lets take a deeper look.

One, you can see I never create any intermediary object, I only call getAllEmployee and then I immediately work on the returned result. I don’t even need any explicit loop for this.

Second, I never explicitly return the Object. Unlike OOP which require us to write return theresult to explicitly tell the program that we want to return a specific Object. Functional language actually return the function itself. We are not returning any object, but anyone who call getEmployeeOnITAndOver35() will know what to do and will get a result of List of employee they want. We are creating a black box that will return a same result when given the same parameter.

The second part is what they mean when they said write What happen. Unlike the OOP where we detailly tell the code How to do the filter.

Note that Java has a more ‘functional-like’ language, like Stream with which we can simplify the previous code to

public List<Employee> getEmployeeOnITAndOver35() {
	List<Employee> allEmployee = getAllEmployee(); 
	List<Employee> ITEmployeeOver35 = allEmployee.stream()
		.filter(employee -> employee.getDepartment().equals("IT"))
		.filter(employee -> employee.getAge() > 35)
		.collect(Collectors.toList());
	return ITEmployeeOver35;
}

this is very close to what Scala has. Java Stream API seem to be directly inspired by how Scala does thing. Adopting the nice thing on Scala directly to Java.

High Order Function

This is the original huge thing that Scala has over Java, while the concept itself has exists on other language, but back in 2004 Java still heavily rely on for and while to do processing.

Scala bring those HOF (High Order Function) to the JVM ecosystem.

Apparently people like it and so Java finally adopt the HOF into the Java starting from Java 8 (2014).

Here are some existing HOF that Scala has (courtesy to Gemini):

FunctionPurposeWhen to Use It
mapTransformation: Applies a function to every element and returns a new collection of the results.When you need to transform the elements of a list, but keep the number of elements the same (e.g., squaring every number, extracting a field from an object).
filterSelection: Selects all elements that satisfy a given predicate (a function that returns a Boolean).When you need to remove elements from a collection that don’t meet a specific criterion (e.g., keeping only positive numbers, filtering out inactive users).
flatMapTransformation & Flattening: Applies a function to every element that returns an inner collection, and then flattens the result into a single collection.When the transformation of one element can result in zero, one, or many new elements, and you want to combine all results into one flat list (e.g., splitting a string into a list of words).
foreachSide Effects: Applies a function to every element.When you need to perform a side effect for each element, such as printing to the console, logging, or writing to a file. It returns Unit (no meaningful result).
reduceReduction: Combines elements of the collection using a binary operator, starting with the first element.When you need to calculate a single summary value from a collection, and the resulting type is the same as the element type (e.g., finding the maximum value, concatenating all strings).
foldLeft / foldRightReduction (Generalized): Combines elements using a binary operator and an initial starting value.When you need to calculate a single summary value, and the resulting type is different from the element type (e.g., summing numbers into a Map, or building a complex object from a list of properties).
findSearching: Returns the first element that satisfies a given predicate, wrapped in an Option.When you only need to check if at least one element meets a condition, and you want the element itself (e.g., finding the first user whose name starts with ‘A’).
takeWhileCondition-based Sublist: Takes elements from the beginning of the collection as long as the predicate is true. Stops immediately when it fails.When you need a prefix of a list that satisfies a condition, and you know the elements satisfying the condition are contiguous at the start.
dropWhileCondition-based Sublist: Drops elements from the beginning of the collection as long as the predicate is true, returning the rest.When you need to remove a known prefix of elements that meet a condition (e.g., removing leading zero-values from a list of measurements).

along with those HOF, Scala also has a concept of side-effect.

The side-effect is a function that doesn’t actually has anything to do with the data processing, instead it allow us to do side thing when processing the data.

Those side-effect is a necessary evil in system, because the fact is we still need to communicate with outside world even when doing the data processing. For example: writing log, hitting API, connecting to database.

Common example is for logging:

def processingData: List<Int> = {
	data
		.filter(d => d > 2)
		.foreach(n => println(s"The values is $n"))
}

As you can see, foreach doesn’t really do anything to the data, but we need it for logging. This is what side-effect do.

Tail Recursive

This is maybe the weirdest thing I encounter from OOP point-of-view. I first stumble on it when learning about Gleam. At first it very confusing, but later on I think this is how we elegantly write a solution.

Lets say we had a function to calculate the factorial of a number. (Excuse my weird approach, this is for sake of illustrating how tail recursive work)

def factorial(input: Int) : Int = {
	input match {
		case n if input <= 0 return 1
		case n => {
			input * factorial(n-1)
		}
	}
}

This seems to make sense isn’t it? to calculate factorial we just had to create a recursive function until the input is 0 which will return 1.

But this will actually cause issue, the fact that we are doing recursive call meaning that we will have multiple function waiting each other until one of them finally stop calling the next recursive. Having multiple function waiting each other will create a stack that seem so useless.

And so tail recursive came to the rescue.

Maybe simplest way I can explain tail recursive is:

A default way a function should call itself when none of pattern match

The previous code can be reworded as following.

@tailrec  
def factorialHelper(n: Int, accumulator: BigInt): BigInt = {  
  n match {  
    case 0 => accumulator  
    // Note that the default function is the very last thing this function will call, so it doesn't have to bother with additional thing and this function can be compressed.  
    case _ => factorialHelper(n-1, accumulator * n)  
  }  
}

As before, this will cause a function that call itself over and over again, the difference is this time we add @tailrec to the function, this will tell the JVM that this function can be compressed. It wont create a stack of function like before, it will smartly (magically? I don’t really know how) make sure it use less stack compared to previous code.

Elegantly return Result/Error

This is one that fumble me the most when I first starting to learn Scala. Moreover learning this directly on ZIO also doesn’t help at all.

When you see functional programming, you will often meet with pattern matching

When I first learn this on another language, this is like one of the wilder idea. Like why even has this? why not just use if (Gleam somehow doesn’t have if )? This is like extra hard way to process something, especially with 1001 way a data should be processed courtesy to product owner.

Luckily, Scala has easier to understand pattern matching. On Scala this is done through familiar switch case statement. That on itself is boring, but the one thing that I want to highlight is how Scala can return a result cleanly.

When running a function, there is 2 way the function could end:

  1. It return value as we expected
  2. It return failure

On Java, the 2nd part is done with Exception and all of its extended family. On Scala, we have another way to semantically show that the function can return the value or error.

Here are type (thanks Gemini)

TypeCore PurposeSuccess RepresentationFailure/Absence RepresentationWhen to Use
Option[A]Represents an optional value (presence or absence).Some[A] (Value of type A is present.)None (Value is absent.)When a result may or may not exist, and the reason for absence is not important.
Either[L, R]Represents a value that is one of two types (typically Error or Success).Right[R] (The successful result of type R.)Left[L] (The error value of type L.)When you need to convey a specific error context/type (the reason for failure).
Try[T]Represents a computation that may throw an exception.Success[T] (The successful result of type T.)Failure[T] (Holds a Throwable exception that was caught.)When wrapping code that is exception-prone (e.g., I/O operations, parsing) and you want to manage exceptions functionally.
Future[T]Represents a result that will be available later from an asynchronous computation.Successful Future (Holds a value of type T when computation completes.)Failed Future (Holds a Throwable exception when computation fails.)When performing non-blocking asynchronous operations (e.g., network calls, long-running tasks).

So, why does this matter?

This pattern force us (or at least guide us) making sure that we handle all possible result.

I think this is the most powerful feature that any Functional Language has. It forced us to create a clean flow that doesn’t have any unknown error that is unexpected.

The issue? on Scala this will cause a deep nesting when there is many possible error we must handle.

Final Thought

I have yet to see the best of Functional Programming, so I can’t really say that this will be the best. For a data processing flow where it is clear what must be done, this is good. But for case where there is many weird and unexpected condition can happen, I think OOP still take the take.

Now about Scala itself, on 2025, it sit on a weird spot. Java has evolve so much that it has many feature that Scala back then exclusively had. I don’t really think there is a strong incentive to write anything with Scala except if the use case have a really strong data processing flow. (Also if you are very comfortable with writing Functional).

Me myself still intend to learn more about this language, especially with its integration on Akka/Pekko and ZIO