Alejandro Martinez

16 January, 2018SwiftFunctional Programming🕐 18min read

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.

LambdaCast

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<T> {
    let value: T
}
let boxed = Box(value: 1)
// boxed type is Box<Int>

You can use generics in types and functions.

func debug<T>(_ something: T) {
    print(something)
}
debug(1)
debug(boxed)

The generic system also allows you to restrict which types are accepted with type constraints.

func isBigger<T: Comparable>(_ l: T, _ r: T) -> Bool {
    return l > r
}
isBigger(1, 2)
isBigger(boxed, boxed) // argument type 'Box<Int>' does not conform to expected type 'Comparable'

In the podcast they explain pretty well the balance between accepting more generic or specific types in your functions. The more generic you go the less you know about the types so the less things you can do with them.

In the previous example the compiler can't use the > operator unless it knows that the values are comparable. Int is comparable so isBigger(1, 2) compiles but our custom Box type is not so isBigger(boxed, boxed) can't compile.

When you add this parametric polymorphism with constraints to the powerful extension system you can start modeling interesting things with Swift.

For example, to solve our error we can make Box conform to Comparable. But of course, Box works with any T so we can't implement a comparation function without any knowledge. We could restrict Box to only work with Comparable types but then we lose the ability to use it for many other types. The solution is to use Conditional Conformance which allows to make Box Comparable only when T is Comparable itself. Really powerful!

One super fancy functional thing that Swift is lacking is Higher Kinded Types. Although it has come up in swift-evolution multiple times it doesn't seem to get enough traction to be a priority yet, or even ever.

10 - Partial Application

a.k.a currying.

You can use it in Swift but the language is not curried by default. For example I recommend you to take a look at pointfree, a website implemented fully in Swift following a pretty cool functional style.

Even if it's not part of the language is still interesting how it lets you do it without getting in the middle.

You can do it as usual with a closure, the shorthand $0 syntax to access the parameters is always nice.

func match(pattern: String, to: String) -> Bool {
    return to.contains(pattern)
}

let pattern = "world"
let a = ["Hello world", "This is my world", "Go away"]
let b = a.filter({ match(pattern: pattern, to: $0) })
// ["Hello world", "This is my world"]

You can go a step further and store a version of match with the pattern already applied.

let matchWorld = { to in match(pattern: pattern, to: to) }
let c = a.filter(matchWorld)
// ["Hello world", "This is my world"]

But you can also create a curry function and use that directly. It gives you a lot of flexibility and allows you to use some functional patterns even if the langauge doesn't support it by default.

func curry<A, B, C>(_ f: @escaping (A, B) -> C) -> ((A) -> ((B) -> C)) {
    return { a in { b in return f(a, b) } }
}
let d = a.filter(curry(match)(pattern))
// ["Hello world", "This is my world"]

12 - Monoids

Now we're getting in the big leagues. The time when everyone starts running away scared of the capital letters M, F. I won't try to explain you what a Monad is :P but I can't avoid leaving here a simple sentence that did the job for me.

Anything that follows this rules [insert rules for X] we can call it [X].

Done.

But back to Swift.

Swift doesn’t have Monoids in the standard library and the community is not super into it. That's understandable as a big part of the community are iOS developers without much FP background. Luckly for us there are some people in the community that are pushing the language into this realm with some success stories.

All of it just by writting the type definitions and, thanks to extensions, adding conformance to them in existing types. (take a look at the libraries used in pointfree).

The issue with this FP categories is that you could consider them like protocols but somewhat at a different level (that's why they are called "higher"). They not only describe the interface of the type but also the rules and laws that has to follow, rules that the Swift compiler can’t check for us. In practice is not that different of a normal protocol as some protocols also have rules.

But the biggest difference is that it's not just a type that has the rules, but a type under some conditions that has them. And it's not rare that a type can have more than one monoid. For example Int has a monoid with plus and zero, and another with multiplication and 1.

If you're interested in this type classes and their rules I recommend you to check Typeclassopedia.

13 - ADTs

Swift provides differend kind of types following the Algebraic Data Types definition.

  • Product type: class, struct, tuples.
  • Sum type (discriminated union): enum with associated values.

This one was funny to listen to because the cast gives examples that are exactly how you would describe enums with assoicated values in Swift. It's really intersting how Swift introduced them as just "better eums" without bothering people with the history or math behind it. And today they are one of the more beloved features of the language.

enum Side<L, R> {
    case left(L)
    case right(R)
}

let next = Side<String, Int>.left("keep going")

Note that this also related directly to the Optional type from the "null and friends" episode.

But these enums would be hard to use if it was not for powerful switches that use pattern matching to unwrapp the data from them.

switch next {
case .left(let text):
    print(text)
case .right(let times):
    print(times)
}

The cast gives alternatives in languages that don't have this feature, but one that they don't mention is how you could have this in OOP by having an empty superclass and subclasses with the data. You don’t have the exhaustive checks, as at any point anybody can create a new subclass, but you are forced to check if it’s an instance of a subclass and cast it to the appropiate subclass to get the values. Something like the as in Swift. Not ideal but useful if you don't have any other alternative.

Going the extra mile Swift can pattern match in other types, like ranges, not only enums. And it even lets you create your own pattern matches with the ~ operator. Not only that but you can also decorate a case with a where clause adding logic in the check that can use the unwrapped values.

case .right(let times) where times > 10:

This episode also talks a lot about "domain" programing that relates to things like phantom types, useful for example for measurements APIs (read Measurements and Units with Phantom Types by Ole Begemann); and about sanetized input (as in Solving the Strings Problem in Swift). I'm really interested in this school of thought about using specific types for your domain instead of using Int/String all over the place.

All of this makes Swift a great language to introducing these functional concepts.

14 - Dynamic and Static Languages

Oh wow, the big and controversial topic is here!

I've always been in the static camp, not only for the strong type system but also for performance and compiling what I write down to the metal.

I strongly believe that in an ideal scenario there are no reasons to pick a dynamic weakly typed language, except that in our reality those languages have great tooling and the huge amount of effort that has been put in them, see Javascript. In my opinion any compiled language could have the fancy things that JS has (reloading, fast iteration...) but it's just not part of the culture (at least in app development, as usual game developers are years ahead of us).

I want the conpiler to work for me, I don't want to be doing its job in my head. If I can make my code work and ensure it doesn't crash or do unexpected things in production why would I pick otherwise?

Usually the issue is about being easier to writte in dynamic scripting languages, but that excuse ends up being just about syntax and libraries. For example Swift has gone really far with this, the syntax is really nice and you can jump out of the restrictions/safety easily (just drop ! in your scripts and done!). With that you can write scripts as fast as with any other scripting language. Also type inference makes you forget about declaring types if that's what you don't like about static languages.

Even the JS community has realised how useful types are and they are putting efforts in amazing things like Typescript and Flow. Even better, a compiler can help you design your programs! Look at Elm for example, it speaks to you in a language that you can understand, without weird errors and it even suggests proper fixes. That's a compiler working for you.

In any case, about Swift. It's a compiled language yes, but it also has a lot of runtime facilities, a REPL, Playgrounds, etc. Who knows what the future will add to this (a typed actor system? easier hot-reloading?).

16 - Functors, 17 - Applicative Functors and 18 - Monads

This episodes are really interesting to understand the concepts but there is not much that is langauge specific. Everthing interesting was alredy discussed in 12 - Monoids.

One thing I realised listening to teh cast is the importance of having the same syntax to declare functions and values. I think a lot of confusion in these episodes come from that. Without that syntax consitency people has a hard time doing composition. I guess that it's because they don't see how a function could be created from other functions without writing it and giving it a name. It gets easier when you consider functions as values so they can be passed around and returned as any other value.

And again, Swift doesn't have HKT but check Emulating HKT in Swift for an exaomple of how they can be introduced at a library level.

Conclusion

Sorry for the long post. I've spend a lot of time listening to the podcast and had so many things to say ^^

I hope this shows you how powerful and great Swift is if you're an imperative programmer with interest in functional programming. I beleive Swift is a great language to start understanding all of these concepts without the need of going full FP and losing the comfort of what you already know.

For the ones that already use Swift, I hope this gives you a better vision on the FP features of our beloved language and more reasons to recommend it to your colleagues so we can grow this amazing community!

And finally, don't be afraid of embracing FP. It's not just the cool fashionable thing to do today, the majoirty of this concepts are super old and proven. The new thing is that we now have them mixed with our impearitve language. Be open minded when some hardcore FP guru comes into swift-evolution to recommend different ways of doing things, we're all here to make a great community and language.

I can't wait to see where Swift goes from here. <3

👉🏻 If you have any feedback you can reach me at alexito4

Alejandro Martinez
alexito4@gmail.com

Buka pintu