Check out my new course Learn you some Lambda best practice for great good! and learn the best practices for performance, cost, security, resilience, observability and scalability.
Read the whole series:
Part 1 — type inference
Part 2 — traits
Part 3 — case class/object (ADTs)
Part 4 — apply & unapply functions <- you’re here
Part 5 — implicits
Last time around we looked at Scala’s Case Class in depth and how it compares to F#’s Discriminated Unions. F# also has Active Patterns, which is a very powerful language feature in its own right. Unsurprisingly, Scala also has something similar in the shape of extractors (via the unapply function).
Before we can talk about extractors we have to first talk about Scala’s object again. Remember when we first met object in Scala I said it’s Scala’s equivalent to F#’s module? (except it can be generic, supports inheritance, and multiple inheritance)
Well, turns out Scala has another bit of special magic in the form of an apply function.
The apply function
In Scala, if you assign a function to a value, that value will have the type Function1[TInput, TOutput]. Since everything in Scala is an object, this value also have a couple of functions on it.
You can use andThen or compose to compose it with another function (think of them as F#’s >> and << operators respectively).
The apply function applies the argument to the function, but you can invoke the function without it.
Ok, now that we know what apply function’s role is, let’s go back to object.
If you declare an apply function in an object, it essentially allows the object to be used as a factory class (indeed this is called the Factory pattern in Scala).
You see this pattern in Scala very often, and there are some useful built-in factories such as Option (which wraps an object as Some(x) unless it’s null, in which case returns None).
String and BigInt defines their own apply function too (in String‘s case, it returns the char at the specified index) .
You can also define an apply function on a class as well as an object, and it works the same way. For instance…
I find this notion of applying arguments to an object somewhat alien, almost as if this is an elaborate way of creating a delegate even though Scala already have first-class functions…
Ok, can you pass multiple arguments to apply? What about overloading?
Check, and Check.
What about case classes and case object?
Check, and Check.
Ok. Can the apply function(s) be inherited and overridden like a normal function?
Check, and Check. Although this is consistent with inheritance and OOP in Java, I can’t help but to feel it has the potential to create ambiguity and one should just stick with plain old functions.
The unapply function (aka extractors)
When you create a case class or case object, you also create a pattern that can be used in pattern matching. It’s not the only way to create patterns in Scala, you can also create a pattern by defining an unapply function in your class/object.
or, if you don’t want to return anything from the pattern.
So, the unapply function turns a Scala object into a pattern, and here are some limitations on the unapply function:
- it can only take one argument
- if you want to return a value, then the return type T must defines members:
- isEmpty: Boolean
- get: Any
side note: point 2 is interesting. Looking at all the examples on the Internet one might assume the unapply function must return an Option[T], but turns out it’s OK to return any type so long it has the necessary members!
Whilst I can’t think of a situation where I’d need to use anything other than an Option[T], this insight gives me a better understanding of how pattern matching in Scala works.
Whether or not the pattern matches is determined by the value of isEmpty of the result type T. And the value returned by your pattern – ie msg in the example above – is determined by the value of get of the result type T. So if you’re feeling a bit cheeky, you can always do something like this:
Since the unapply function is a member on an object (like the apply function), it means it should work with a class too, and indeed it does.
As you can see from the snippet above, this allows you to create parameterized patterns and work around the limitation of having only one argument in the unapply function.
You can nest patterns together too, for example.
Here, the Int pattern returns an Int, and instead of binding it to a name we can apply another pattern inline to check if the value is even.
And whilst it doesn’t get mentioned in any of the articles I have seen, these patterns are not limited to the match clause either. For instance, you can use it as part of declaration. (but be careful, as you’ll get a MatchError if the pattern doesn’t match!)
Primer: F# Active Patterns
Before we compare Scala’s extractors to F#’s active patterns, here’s a quick primer if you need to catch up on how F#’s active patterns work.
Like extractors, it gives you named patterns that you can use in pattern matching, and comes in 3 flavours: single-case, partial, and multi-case.
You can parameterise a pattern.
If a pattern’s declaration has multiple arguments, then the last argument is the thing that is being pattern matched (same as the single argument to unapply); the preceding arguments can be passed into the pattern at the call site. For example…
If you don’t want to return anything, then you can always return () or Some() instead (partial patterns require the latter).
You can also mix and match different patterns together using & and |. So we can rewrite the fizzbuzz function as the following..
Patterns can be nested.
Finally, patterns can be used in assignment as well as function arguments too.
extractors vs F# Active Patterns
Scala’s extractor is the equivalent of F#’s partial pattern, and although there is no like-for-like replacement for single-case and multi-case patterns you can mimic both with extractor(s):
- an extractor that always return Some(x) is like a single-case pattern
- multiple extractors working together (maybe even loosely grouped together via a common trait) can mimic a multi-case pattern, although it’s up to you to ensure the extractors don’t overlap on input values
Whilst it’s possible to create parameterized patterns with Scala extractors (by using class instead of object), I find the process of doing so in F# to be much more concise. In general, the syntax for declaring patterns in Scala is a lot more verbose by comparison.
The biggest difference for me though, is that in F# you can use multiple patterns in one case expression by composing them with & and |. This makes even complex patterns easy to express and understand.
- Scala School : apply
- SO : What is the apply function in Scala?
- The factory pattern in Scala
- The Neophyte’s Guide to Scala : extractors
- A tour of Scala : extractor objects
- Scala Saturday – Pattern Matching, Part 4, Extractors
- From F# to Scala : case class/object
I specialise in rapidly transitioning teams to serverless and building production-ready services on AWS.
Are you struggling with serverless or need guidance on best practices? Do you want someone to review your architecture and help you avoid costly mistakes down the line? Whatever the case, I’m here to help.
Check out my new course, Learn you some Lambda best practice for great good! In this course, you will learn best practices for working with AWS Lambda in terms of performance, cost, security, scalability, resilience and observability. Enrol now and enjoy a special preorder price of £9.99 (~$13).
Are you working with Serverless and looking for expert training to level-up your skills? Or are you looking for a solid foundation to start from? Look no further, register for my Production-Ready Serverless workshop to learn how to build production-grade Serverless applications!
Here is a complete list of all my posts on serverless and AWS Lambda. In the meantime, here are a few of my most popular blog posts.
- Lambda optimization tip – enable HTTP keep-alive
- You are thinking about serverless costs all wrong
- Many faced threats to Serverless security
- We can do better than percentile latencies
- I’m afraid you’re thinking about AWS Lambda cold starts all wrong
- Yubl’s road to Serverless
- AWS Lambda – should you have few monolithic functions or many single-purposed functions?
- AWS Lambda – compare coldstart time with different languages, memory and code sizes
- Guys, we’re doing pagination wrong