Learning Functional Programming with Scala
In this blog post I will write about my journey learning functional programming with Scala. I have a very object-oriented background in Java. To accompany me on this journey, I have decided to take this Coursera course.
I feel like it’s one thing to learn the theory and another thing to apply it. For this reason, I have also decided to build a working application after I completed my course. This is further detailed in the final chapters.
So my goals are:
- Complete the Coursera course assignments, which features university level content
- Write an application that reflects some of what I learned in this course
- Interoperability with Java. This allows me to use my favourite libraries.
- I am genuinely interested in picking up more functional programming principles
- The job market in the Netherlands seems to need Scala developers
- According to StackOverflow 2020, it’s one of the highest paid languages :D
- In-demand + High Salary = Profit???
The Scala language reminds me a lot of Kotlin. Types are written in a format like “var: Type”. A lot of things in Scala are implicit. For instance, the last expression of a function is its return.
Everything is an expression
In Scala, everything is an expression. Even if-statements! This can be shocking for a Java developer. Luckily, I have some experience writing Kotlin, so this concept is not that new to me. Here’s an example of an if expression:
Functions, Blocks & Lexical Scope
Functions can exist within functions with Scala. This can be used to hide parts of the surface area of a function, while still be able to re-use a private function. Here is an example of calculating the square root of a number by approximation and recursion. The ‘abs’, ‘isGood’, ‘improve’ and ‘sqrtIter’ functions are not accessible outside the scope of the functions.
Def vs Val
Scala knows a big difference between definitions(defs) and values(vals). Defs are just definitions to what a variable should be evaluated against. This means that defs follow a lazy evaluation strategy that evaluates a def as it is needed. Vals on the other hand are evaluated on the spot. It properly reflects what the value of a variable is.
Functional programs embrace the idea of immutability. That means that you don’t mutate the state of data(e.g. person.setName(String)), you return new versions of data(e.g. person.copyWithName(String)). It seems inefficient, but it grants a heap of benefits.
- Thread Safe: Immutable objects are thread safe by definition
- No invalid state: Once you validate the initial state of an object, you can be assured that this object always remains valid.
- Simpler to test: Because objects don’t change internally, they are by far easier to test, as their methods are always reproducible.
Functional programming embraces the idea that functions should be pure. That means that:
- The function must not rely on any code outside its scope, or hidden from view.
- The function must not modify any code outside its scope.
- The function must always evaluate to the same result, given the arguments are the same.
Recursion is not a new concept for me. But sometimes, I find it hard to wrap my head around it. Seeing that in pure functional programming there is no notion of a loop, this will be fun. In the exercises below, you will see me do the following using recursion:
- Implement the logic for Pascal’s Triangle
- Balance parentheses in a list of chars
- Counting the possibilities of change, given a certain list of coin types
- Calculate factorial
Recursion can generate a lot of load on your stack trace, causing stack overflow errors. Compilers & runtimes have implemented the concept of tail recursion to tackle some of these problems. Tail-recursion recycles the originating call graph, effectively making the call a for loop in disguise. Tail-recursion can only be applied if the recursive call is the last expression in a function and no other operators are applied to the recursive call. Beware, tail recursions introduce another level of cognitive load, as the function typically becomes harder to read.
Higher-order functions are functions that take other functions as parameters and/or returns other functions. Here’s a quick example. Let’s say I have these functions:
If I wanted to write a function that sums up all the cubes or factorials of a number, I can write something like this:
Higher-order functions enable the concept of currying. *Insert lame joke about Indian food here*. It’s basically the concept of nesting returning functions. A function can return a function that returns another function.
Now let’s see if we can rewrite factorial using the curry technique:
At the end of week 2, I implemented the concept of a functional set. The general notion of a functional set is that: We represent a set by its characteristic function, i.e. its `contains` predicate. This was a pretty fun assignment, as it really shows the elegance of functional programming
Traits, Generics, Classes and Objects. Huh?!
In the course I learned how to use the basic object-oriented primitives in Scala. I am very familiar with these concepts. However, in the course I noticed that the instructor also applied more declarative/functional principles to his code. For instance, he implemented a Binary Tree with classes and objects. However, he did not use imperative code to do so. Instead of updating a tree’s state, he would return a new instance of the tree. This by nature is purely functional. Wow. There are no side effects. The data is immutable. But, wow. Here is my example of an immutable List.
Decomposition vs Pattern Matching
I use decomposition a lot. One of the problems I ran into while developing my own open-source project was the simplification of expressions. It turns out that the use of decomposition was not a feasible solution for what I wanted to do.
Pattern matching helps you decompose and navigate data structures in a very convenient and compact syntax. It looks like a Java switch statement, but is definitely not. Switch could never! The pattern matching feature looks very beautiful to me.
Pattern Matching List
You can also pattern match against Lists, Strings and even RegEx patterns. Here I use pattern matching to implement the Insertion Sort. As you can see I match against the head and the tail of the list using ‘::’
In week 5, I implemented (and barely understood) Huffman coding. It is essentially a way to compress and decompress Strings by assigning every character in a String a weight. This weight represents how many times a character is used within a String. The characters are put into a certain code tree, which is contains Fork nodes and Leaf nodes.
Here is my solution(got an 8/10)
Tuples are a concept that does not exist in the Java world. They allow you to define a data structure that allows you to return multiple values from a function. This is one of my favourite parts of the Scala language. You can then destructure or pattern match against tuples like what I did in this Merge Sort implementation.
Map, Filter, Reduce
I present to you, the Map-Filter-Reduce sandwich.
These three functions (along with forEach) form the basis of most functional operations.
Map allows you to transform data. Filter allows you to weed out unwanted data. Reduce allows you to summarize data.
These three methods are very powerful. And they form a basis to what you can do with sequences of data.
Week 6: Scala on the Cloud
In my last week picking up Scala, I decided to build an application that runs on the cloud. I decided to do so in the form of a cloud function, considering that this is functional programming 😉. I wanted to create a service that analyses the contents of Word, PDF and Text files and extracts key information about those files. The program would essentially perform sentiment analysis on the text content of the file and respond with the following contents:
- The language of the file
- The sentence with the highest magnitude
- The most positive sentence
- The most negative sentence
- The general tone of the document
This service will use the Google Cloud Natural Language API to perform the analysis.
Used Functional Programming principles
I applied the following principles to my program, located here:
- The creation and decomposition of tuples
- Higher-order functions
- RegEx pattern matching
- Nested functions
- Awesome Scala syntax sugar. Like that cool looking ‘else-try’ block
I feel like these principles reflect my favourite parts about functional programming.
I feel like I have learned a lot about functional programming. At first, I didn’t like it, because of the initial learning curve. I felt everything was hidden behind some weird notation. After I understood what some of these notations meant, I grew a lot of appreciation for functional programming. I will definitely use more of these practices in my professional career. Who knows, I might even use Scala in a professional project 🤷🏾♂️.
Connect with me ❤
I’m usually down for a spar session. Maybe even a new code homie?
- Check out my code on GitHub: https://github.com/RyanSusana
- Follow me on Twitter: https://twitter.com/RyanSusana
- Send me a message via my website: https://ryansusana.com