I've been writing Elixir for my main backend needs for the past few months and I've really liked it — specially its syntax. In fact, I've gotten so into its syntax that I've often found myself trying to write Swift as if it were Elixir.

Oops!

So I decided to experiment a little bit with bringing my favourite syntax from Elixir over to Swift natively.

Disclaimer: This is just an experiment. To get this to work on an actual project, a lot more work needs be done.


Elixir is a functional language that's gained a lot of traction in the past few months thanks to its simplicity and core features, such as the supervision tree.

Every type in Elixir is a value, which means that it gets copied when passed to a function — the function gets a new copy of the value, which it can then modify without affecting the original instance.

> aMap = %{a: 1}
  %{a: 1}

> bMap = Map.update(aMap, :a, 0, &(&1 * 3))
  %{a: 3}

> aMap 
  %{a: 1}

> bMap
  %{a: 3}

Also, Elixir has this great "pipe" operator that lets you write cleaner code and streamline data manipulation in a really easy way:

> cMap = aMap |> Map.update(:a, 0, &(&1 / 3))

The "pipe" (|> ) operator takes the value on its left and passes it to the function on the right as its first parameter. This is included as part of the language and works with any function.

Let's bring that to Swift!

In this example, I'm going to use two simple structs (value types in Swift):

struct Company {  
    let name: String
}

struct Person {  
    let name: String
    let work: Company
}

let apple = Company(name: "")  
let joe = Person(name: "Joe", work: apple)  

A characteristic of functional programming languages is that they strive for immutability. Vaues should not mutate during program execution, but copying values and modifying them is good.

Tipically, if we wanted to update joe's name, we'd do something like

joe.name = "Joe Wood"  

If we did this, the Swift compiler would raise two errors:

  1. the name property on Person is declared as a constant (let), which means it can't mutate during execution — a change to var is suggested.
  2. the joe instance was declared as a constant (let), which means it can't mutate during execution — a change to var is suggested.

What we don't want, though, is enable the ground to shift under our feet, so let's keep everything as a constant.

Another approach would be to create a function that did so:

extension Person {  
  mutating func update(name: String) {
    self.name = newName
  }
}

But again, that mutating keyword there is a code-smell in the functional world. The way to do this the functional way is:

extension Person {  
  func update(name: String) -> Person {
    return Person(name: newName, work: self.work)
  }
}

We created a function that changes only one property on the instance — it receives the new value for the property, and returns a whole new instance of the type with that value replacing the original one.

joe.update(name: "Joe Wood").name // Joe Wood  
joe.name                          // Joe  

Now, the problem with this is that the function that we created is operating on the type-instance.

Note: In Elixir, a map literal can be written as %{a: 1}, but to transform and operate on that instance, we have to use the Map module. That's kind of what we want to achieve here.

We want that the Person struct (or module, if you're thinking in Elixir) to have a series of methods that receive Person instances to operate with.

In order to achieve that, we need to do a bit of groundwork.

Lenses

There is a concept in functional programming called "data lenses". Explaining in detail what a data lens is, is way out of scope of this post, but you can read more about it here and here, and generally, just searching "lenses functional programming" in any search engine.

The idea is to have a data type (a Lens) that is generic and lets us get and set (a.k.a. zoom to) information on the type that the Lens is generic to.

Example:

struct Lens<Whole, Part> {  
  let get: (Whole) -> Part
  let set: (Whole, Part) -> Whole
}

let personNameLens = Lens<Person, String> (  
  get: { $0.name },
  set: { Person(name: $1, work: $0.work) }
)

personNameLens.get(joe)                  // Joe  
personNameLens.set(joe, "Joe Wood").name // Joe Wood  

That's a Lens. However, we'll only need a subset here, so let's define a new data type that gives us only the set method:

struct SetLens<Whole, Part> {  
  let set: (Whole, Part) -> Whole
}

To update a Person instance, we can do this now:

let setPersonNameLens = SetLens<Person, String> (  
  set: { Person(name: $1, work: $0.work) }
}

setPersonNameLens.set(joe, "Joe Wood")  

Now lets do a bit of work to move the interface up to type-level by making our SetLens a static property of Person, for brevity:

extension Person {  
  static let SetName = SetLens<Person, String> (
    set: { Person(name: $1, work: $0.work) }
  ) 
}

Person.SetName.set(joe, "Joe Wood")  

We still have the problem that we'd have to interact with the SetLens directly. To mimic the Elixir behaviour, we need to wrap the SetName property on a static function that returns a function that when called, calls the set method on the lens.

extension Person {  
  static func Update(name: String) -> Person -> Person {
    return { p in SetName.set(p, newName) }
  }
}

We define a static function that takes a new name as a parameter, and returns a function that takes a Person and returns a Person. Calling this function just passes the values to the SetLens.

Now, let's introduce the pipe operator:

precedencegroup Applicable {  
    associativity: left
}
infix operator |>  
func |> <A, B>(a: A, rhs: (A) -> B) -> B {  
    return rhs(a)
}

This new custom operator takes a value to the left and a function on the right, and just calls the function on the right passing the value on the left. How convenient!

We can now do this:

joe |> Person.UpdateName("Joe Wood")  

Ta'da!

Doing the appropriate amount of work, we could be writing this really soon:

joe  
  |> Person.Update(name: "Joe Wood")
  |> Person.Update(work: Company(name: "Apple"))

And voilá, we've migrated one of Elixir's core features to Swift, while maintaining type-safety!

Final comments

Admittedly, it took a long of work just to migrate this one feature over to Swift. This does not mean, though, that it can't be done for big code-bases and "real apps." One of the best features of Swift is that the compiler is real smart and can tell us on compile-time if we messed something up. ❤️

This was just a small exercise, but I do appreciate the similarities that Swift and Elixir have, and the fact that both languages are flexbile enough that we can do these sort of things. Pretty cool.


Hey! I'm writing a book on best practices for iOS development with Swift, and it's called (drum roll) Best Practices for Developing iOS Apps with Swift.

Subscribe below to get a 50% off coupon code when the book is released — also get the first chapter for free when it is ready!