LambdaCast examples of functional programming in Swift
I recently discovered the podcast LambdaCast, a really interesting podcast about functional programming with hosts that are at different points on the path to functional programming. This gives an interesting view to the listener that can get explanations from starters and experts. They usually talk about FP in terms of how to use them in imperative land, mainly with Javascript and C#. Thatās nice for many people, but every time they give an example my brain starts thinking about how Swift deals with it.
So at some point I started taking notes comparing what they talk with Swift and reaffirming my opinion on what makes Swift a good language to dip your toe in FP but also on the features that Swift lacks in order for it to be consider a strong functional language.
In this posts I just want to share my notes of each episode to try to give to the world more reasons to use Swift :) I really recommend to listen to the podcast as Iām not gonna do a complete summary of it, Iām just gonna comment on the topics as if you were listening to the episode.
4 - Higher-Order Functions
Because Swift has functions as first-class citizens you can just store them in variables, pass them around as arguments to other functions, etc.
func doManyTimes( times: Int, job: () -> ()) { for _ in 1...times { job() } }
func sayHi() { print("Hi!") }
doManyTimes(3, sayHi)
Furthermore the Swift standard library provides many of the usual functional higher order functions like map, reduce, filterā¦ they've become an intrinsic part of the language as named parameters are. It's something that has pushed strongly a more functional style into the community.
[1, 2, 24, 200, 5] .filter({ $0 > 10 }) // [24, 200]
As the previous examples show, you can easily pass named functions by just using it's name (without the parenthesis) or even just create a closure on the fly.
In the episode they talk about an example where you want to sum the elements of an array, for that you need to pass +
to as a function to a high order function (reduce
). That forces some languages that usually only have the operator to have a redundant "plus" function. I wanted to highlight this as it's something they don't mention: other languages, Swift for example, just treat the operators as special names for functions. That allows you to use them as any other function and pass them around. With this you can literally just pass +
into reduce
and it just works.
[1, 2, 3, 4].reduce(0, +) // 10
No need to duplicate functions and operators, or to convert between them. Obviously the operator still has to match the type signature, in this case + accepts two numbers and returns a number so it matches.
This is just one of the first niceties that the languages has to offer to FP.
5 - Immutability
Swift considers mutability as part of the language model so it has direct support for it. For example, structs are (usually) considered immutable value types. A struct
is copied around and can't change, it behaves in the same way as if it was just an Int
. The direct opposite are classes which have identity and so are shared and can be modified.
But Swift doesn't stop there. Swift var
and let
declarations for "variables" have an impact on the value of what are holding directly. This is such an important aspect of the langauge that many of us tried to explain it with our own words years ago. My take in Understanding Value and Reference Types in Swift.
let
makes the content of the variable immutable. For value types, as the content of the variable is the value itself, it makes the value immutable.
struct Value { var a: String }
let a = Value(a: "Hi") //a = Value(a: "b") cannot assign to value: 'a' is a 'let' constant //a.a = "c" cannot assign to property: 'a' is a 'let' constant
Note how even with Value.a
defined as var
the compiler doesn't let us modify it because we declared the local variable let a
as immutable. We could declare it as var
and it would would allows us to modify it.
var b = Value(a: "Hi") b = Value(a: "b") b.a = "c"
The nice thing about this, and what sets Swift apart from other mutability implementations, is that even if you can modify that struct in place immutability is still there. Conceptually what is happening is that you are creating a whole new value and assigning it to var b
again. If someobdy else had a variable pointing to that b
they wouldn't have noticed anything. Immutability still holds for them.
var b = Value(a: "Hi") let bCopy = b b.a = "c" b.a bCopy.a
This also happens implicitly when passing values to functions.
For reference types the "content of the variable" is just a pointer to the instance so if you use let
you can modify the object properties but not what the variable points to.
class Reference { var a: String
init(a: String) { self.a = a } }
let r = Reference(a: "Hi") //r = Reference(a: "b") cannot assign to value: 'r' is a 'let' constant r.a = "b" // this is fine!
The difference here is that if somebody had a variable pointing to the same instance, as the changes are shared, she would have now also a different value. Basically this is the behaviour that everyone is used to deal with with classes and objects.
This mutability model is one of the most powerful feature of Swift. And the fact that the compiler understands value type semantics means that you get the copy on structs for free. Without copy constructors, without having to pass all the parameters with a slightly modificaiton in a new instanceā¦ you just modify the value in place and that gets you a new copy with the new value automatically. The catch is that it only does it with value types (structs, enums and tuples), if you want to have copy behaviour for classes then you need to implement it yourself, at least for now.
The value semantics are a deep part of the language, the standard library data structures like Array
and Dictionary
are structs themselves so they are part of this immutability and value semantics model. Under the hood they use a technique called "copy on write" to avoid doing wasteful copies when is not necessary. The nice thing about immutability is that as long as it appears to be immutable to the exterior world, internally you can do any optimisations you want.
One feature the language lacks is a general deconstruction syntax. Tuples have it but you can't do it with arrays for example. That means that the typical FP pattern of (head, tail)
to iterate a list is not used in Swift.
let tuple = deco() tuple.0
let (num, str) = deco() num
Although this immutable world is really nice, and the language makes it easy to use, the reality is that in general application developers still have to deal with stateful and mutable UI frameworks (we all have a love-hate relationship with UIKit). The functional UI paradigm (redux, react, elm) is still not established in our community, mainly because the platform vendor already provides with good enough tools.
6 - Null And Friends
I think this episode is the first one that they mention Swift and Rust, that's great!
In my opinion Optional<T>
is the best thing Swift introduced. Period.
Explaining Optionals it's always been interesting to me as it's a super simple concept that for some reason most imperative programmers have a really hard time to grasp. Too many years of OOP have these side effects.
But it's also an issue with how usually people explain it. Is really fancy to say "null doesn't exist" but that just gives you a WTF from your colleagues. I found it better to try to explain it in another way, saying that null becomes part of the type system and, more importantly, things are non-null by default.
And that second part is the most important one. Optionals are best used when they are avoided. There are other languages that lack optionals and they are introduced with libraries in the form of Optional/Maybe types, but if the language still considers null a valid value for any type having this optional library is quite useless as time passes.
So in Swift nullability is acomplished with the Optional type, suggared as ?
, which is conceptually implemented simply as an enum
with two cases. Furthermore, Swift accepts that people is used to the concept of "null" so it treats nil
as Optional.none
. Between nil
and ?
it's rare that people uses directly Optional at all.
One of the concerns about languages with this strict type system is that it becomes a burthen for the developer. In my experience if the language provides good support for it there is no major issue. You may suffer the first days but once you become used to think in these terms you find yourself in a better position than ever before.
In Swift it isn't a burthen mainly for two reasons:
As I mention, Optional is an enum with compiler magic that gives you sugar to work with it, with pattern matching and helps wrapping objects into it (?
). It also as a subtype relationship so you can pass non-optional types as arguments of optional types without having to wrap it yourself.
But more importantly, after you start thinking in terms of nullability you avoid using it. Your code should only deal with optionals when it's absolutely necessary. It may seem that 99% of the code has nulls (counting the NullPointerExceptions of any Java codebase would corroborate this) but that's just because your language guides you towards that status-quo. After some time with non-nullability by default you realise that it's far from the truth. Nulls stay in the appropiate layer and just escape when is necessary to indicate the lack of a value.
Swift can also work with a Result type but itās not part of the standard library, in fact this has always been a huge debate in the community. The reason being that Swift introduced an error system that pretty much replaces Result with language specific keywords. It brings nice things but a lot of people still uses third party result types, specially because swift error system doesnāt work in an asynchronous context. The asynchronous story in Swift is starting to get shape now so it's expected that when is completed Swift error system will work better with async code. I would personally have prefered a model where the Result type was the base of the error system but with sugar on top of it, but that ship sailed ages ago.
It's really important to mention how Apple made a huge effort with nullability retrofeeding nullabilty annotations to Objective-C and to all their platform frameworks so when using them from Swift you automatically work with optionals when a method returns nil. This has allowed Swift developers to taste how a world without "null" looks like.
7 - Recursion
As in other imperative languages it is supported in Swift but is not something that is used often. Without deconstructing and the lack of tail call optimisation it's not the ideal way of implementing algorithms.
And of course the function declaration syntax is far from being based on pattern matching like FP languages.
The episode talks about linked lists vs. arrays to facilitate recursive algorithms (taking head and recursing over the tail). This could be done in Swift with an array. ArraySlice is a view into a portion of the full array, so you can write the algorithm nicely without having to deal with multiple copies of the array thanks to copy-on-write.
As a side note, Rust has pattern matching on slices which helps with this type of algorithms.
9 - Polymorphism And Abstraction
Swift has a quite powerful type system with parametric polymorphism in the shape of generics, in fact all the previous types mentioned in this article (Optional, Array, Dictionary) are generic.
struct Box
If you liked this article please consider supporting me