Notes: 'Programming Scala'
These are from a textbook written in 2009! But noting things that should still be relevant in 2021.
Annotations
@switch
guarantees that a pattern match statement is exhaustive at compile time and provides some optimizations if certain conditions are met, e.g., the case
s are relatively simple (no if
conditions, type checks), there are more than 2 case
s. I *think* if your code already fulfills these conditions, the compiler will still optimize your code without the annotation. Also, with IDEs like IntelliJ that have strong Scala support, they should already provide warnings if your pattern matches are not exhaustive.
Functional Programming
FP comes from mathematics and has been a programming paradigm explored in academic computer science spaces for decades. In recent decades it’s become more popular in software engineering.
FP stems from the mathematical concept that functions are pure, i.e., they have no side effects. This means the function should be guaranteed to behave and give the same output every time with the same input, and the context does not matter. Contextlessness emphasizes the notion of referential transparency.
Extending from that idea are the building blocks of functions in programming: variables. Variables, by their very name, in programming can have different values and are typically mutable. In FP, however, they are immutable. This is very different from object oriented programming that thrives with mutability.
In FP, functions are first-class citizens. That means they can be treated as values and are composable. You can chain functions together, pass in functions as inputs to other functions, and return functions as outputs from other functions. A function that does just that — receives a function and/or returns a function — is called a higher-order function.
There are some major advantages to FP. With immutability, problems in multithreaded programming that arise from accessing mutable variables just go away. Beyond multithreading, debugging in general becomes easier and more predictable because of the purity of functions and the less context you need to understand how pieces of code work. Problems that arise from unintended state change in one area with mutable variables due to intended state change in another area with the se variables also go away. We don’t need to implement safeguards for state changes in data structures since the default is no mutable state access (less boilerplate).
One downside to FP is that with immutability, you need to create many copies of data. That can get expensive, but if your programming language optimizes its data structures to compensate for that, generally it’s not a problem. There are exceptions, however where mutability still gives exponential optimization benefits, e.g., with certain serialization implementations for large scale data processing.
Similarly, for/while loops are discouraged since they rely on mutability. Recursion is favored, which is promotes immutability but is generally harder to write in avoiding stack overflow and less performant in invoking the same function multiple times. Your programming language may still be able to optimize for recursion — Scala can for tail-call recursion, which internally converts the code into a loop.
FP in Scala
Memoization
Even in “pure” functional libraries, it is common to perform internal optimizations like caching previously computed values. This is called memoization. While caching introduces side effects since the state of the cache is changed, the end user (the programmer) shouldn’t be aware of it.
Traversal
Mapping over a collection returns the same number of elements that were originally given.
Reducing and Folding allow you to “shrink” a collection to a smaller collection or a single value. Reducing starts with the first element in a collection, while folding relies on a provided seed value. Consequently, reducing throws an exception if the collection is empty; generally folding is safer and preferred.