Scala at Light Speed, Part 3: Functional Programming
Check out this post to learn more about functional programming in Scala.
Join the DZone community and get the full member experience.Join For Free
This article series is for busy programmers who want to learn Scala fast, in 2 hours or less. The articles are the written version of Rock the JVM's Scala at Light Speed mini-course, which you can find for free on YouTube or on the Rock the JVM website in video form.
This is the third article of the series, which will focus on Scala as a functional programming language. You can watch it in video form here or in the embedded video below.
So far, we've covered:
- How to get started with Scala
- The basics: values, expressions, types
- Object-orientation: classes, instances, singletons, methods, and basic generics
You may also like: Scala at the Speed of Light, Part 1: The Essentials, and Part 2: Object Orientation
What Is a Function?
From the previous part, you remember that Scala has the special
apply method that allows instances of classes (or singleton objects) to be invoked like functions:
Also, let's recap a few points:
- Scala runs on top of the JVM.
- The JVM was built for Java, which is an OO language.
- Scala is a functional programming language, meaning we work with functions like any other values: we pass them around, compose them, return them as results.
To fulfill the functional programming ideal, Scala comes with the following traits (simplified):
Because apply allows us to invoke instances like functions, we can easily write:
We've just defined a function!
Scala function values are instances of Function1, Function2, ... Function22, which are nothing but simple traits with an
Looks Like Functional Programming
In functional programming, we work a lot with function values. Constructing them as an anonymous class every time would be a pain. Scala has syntactic sugar for creating function values:
The type of function defined above is
Function1[Int, Int], or
Int => Int for short (looks similar to Haskell in that regard).
For those with real typing anxiety, Scala has an even more shorthand notation:
As long as the compiler knows the function type (as I explicitly declared), you can use underscores for each different argument of the function. This shorthand notation is particularly useful for long method chains with function arguments.
Methods or functions taking other functions as arguments or returning functions as results are called higher-order functions (HOFs).
Some classical HOFs we use a lot with collections are
filter. Let's take them in turn as examples.
map method will take a function as an argument and will return a new collection containing the applications of the function on every element:
flatMap method takes a function from an element to another collection. The applications of the function on every element will return many small collections, which will be finally concatenated in the returned collection.
filter method takes a function from an element to Boolean, and returns a new collection containing just the elements for which the function returns true.
filter is a powerful technique to manipulate collections. An example: all the pairs from combining a number from the list [1,2,3] with a character from the list [a,b,c]:
So, for every number, we take the list of characters, and for every one, we combine the number and the letter.
This is the style of thinking we use in Scala and functional programming. Instead of iterating through a collection and manually constructing the values, we are manipulating the existing collections through their higher-order functions. Of course, "manipulating" is an improper term because we work with immutable data structures, so we always return a new collection.
These map/flatMap/filter chains can be very long in practice, and thus become increasingly hard to read. Scala offers another syntax sugar for these large map/flatMap/filter chains in the form of for-comprehensions:
Misperception to dispel right off the bat: this is not a for loop. It's an expression because it reduces to a value. The value is computed by the map/flatMap/filter chain, which the compiler reconstructs for you when you write a for-comprehension. If your data structure doesn't offer the appropriate
withFilter methods, it will not be eligible for for-comprehensions and the compiler will tell you that.
A Brief Collections Overview
By far, the most common data structure is a list. In the standard library, it's implemented as a singly linked list. The fundamental methods are
head (first element),
tail (remainder of the list), and
:: (prepending an element).
The supertype for all ordered collections of elements is
Seq (for sequence). The fundamental method is the
apply method, which accesses an element at a given index in the sequence.
For large sequential data in memory,
Vector is a fast Seq implementation (implemented as a full 32-ary tree). It has the same API as a Seq.
Sets are also useful, because they contain no duplicates.
Set is just a trait, so the
Set companion's apply method returns some implementation of
Set (usually some hash set). The fundamental methods are
contains (check if an element is in the set),
- (for adding and removing elements).
Ranges are useful for generating numbers. They have the same API as a Seq, but don't need to actually contain all the numbers.
Don't ask about the
to operator above (for now). Notice instead that we can convert in between various collections with
Finally, multi-type collections. Starting with
tuples, which are groups of values under the same value (Python folks should be familiar):
And for key-value associations, we have
maps with their fundamental methods exemplified below.
In the next part, we will talk about one of Scala's most powerful features: pattern matching.
Opinions expressed by DZone contributors are their own.