acb's technical journal

Towards a more functional Swift

Swift came into the world as an interesting chimera; fully formed, with a list of interesting and innovative language features not seen before in the Objective C world, borrowed from other languages. Refreshingly, a few of these borrowings were from the world of functional programming. This is not entirely new to Objective C; since the arrival of the block syntax, some basic functional programming has been part of Objective C, at least with add-ons like BlocksKit; Swift, however, took it half a step further; borrowing from the likes of Haskell and Scala, it introduced concepts such as Optional types and pattern-matching. And then, as if not to scare the horses imperative-OO programmers, it stopped short, leaving the minority of seasoned monad-wranglers a little frustrated.

In a sense, one could see the rationale behind this; the iOS and OSX developer ecosystem is an overwhelmingly Objective C-based one, i.e., one grounded in Smalltalk-style object-oriented programming underpinned by the veritable C language (a language developed in the 1970s as a more portable, and higher-level, alternative to assembly language). Functional concepts have made it in slowly, in the form of blocks and closures; filtering into the Cocoa developer mind-set, at first as a variant of that old warhorse, the C callback function, with moderately functional semantics gradually seeping in from JavaScript (i.e., variable-capturing closures) and Python (map, filter and reduce). Then came along Swift, introduced at WWDC 2014, with its own learning curve, and alongside the usual raft of new iOS and OSX technologies. This was a lot to digest in one go, and throwing in category theory would have only confused things.

Gradually, though, awareness of functional programming techniques has spread throughout the Swift community; at developer meetups and in blogs, the denials of the necessity of such new-fangled ideas, and defences of perfectly good object-oriented techniques have given way to exploration of functional ways of doing things. The language has moved forward as well; when 1.2 came out a few months ago, for example, it brought with it the flatMap function, a key building block of various kinds of chainable operations. However, there is still some way to go. Which is why I have written this post as a plausible, and somewhat subjective, roadmap for some of the ways in which Swift could (and, in my opinion, should) develop.

The ideas here are influenced by languages I have worked with; in particular, there is a fair amount of Scala influence here (as Scala's treatment of functional concepts maps reasonably cleanly onto Swift's way of doing things in many cases) and, to a lesser extent, Python.

if let ... as a comprehension

Optionals (i.e., typed containers containing either nothing or one value of a type) are a key part of the Swift way of doing things. Being containers, they may, of course, be mapped over, and Swift provides ways of chaining optionals, following a sequence of operations, each of which may or may not return a non-empty value, and breaking the sequence at the first empty operation. One of these is the if let sequence; i.e.,

func maybeDoSomething(maybeA: Int?, maybeB: String?) -> String? {
  if let a = maybeA, 
    b = maybeB {
      return doSomething(a,b) // returns a String
  }
}

Given that the two Optionals, maybeA and maybeB, are containers which may be mapped (and flatMapped) over, this looks like it ought to be equivalent to the following:

func maybeDoSomething(maybeA: Int?, maybeB: String?) -> String? {
  return maybeA.flatMap { (a:Int) -> String? in 
    return maybeB.map { (b:String)->String in 
      doSomething(a, b) 
    }
  }
}

Seeing it written out this way, though, raises the tantalising possibility of applying it to other mon containers one can map over, such as Arrays; were the two forms equivalent, one could do something like this:

let arr1: [Int] = [1,2,3]
let arr2: [Int] = [5,6]

if let a = arr1, b = arr2 {
  println("\(a*b)")
}
/* prints 5, 6, 10, 12, 15, 18 */

Though one cannot; this is not valid Swift syntax. (And even if it were, may be argued that if let value = array would be rather odd syntax.)

Of course, there is a similar syntax for arrays and sequences with generators; the for _ in _ syntax, familiar from Objective C; which would look slightly less weird if extended to Optionals; we can already do things like:

func doSomeThings(arr: [Int], opt: Int?) {
  for a in arr {
    if let b = opt {
      doSomething(a, b)
    }
  }
}

By extending Generator semantics to Optionals and expanding the for...in statement, one could unify this into:

func doSomeThings(arr: [Int], opt: Int?) {
  for a in arr, b in opt {
    doSomething(a, b)
  }
}

Given that, in this hypothetical parallel-universe Swift, the enhanced, unified version of for..in has usurped a lot of the functionality of if let, it would make sense to go the whole hog and add the where clause to its syntax, allowing arrays to be filtered upon iteration, like so:

func doSomeThings(arr: [Int], opt: Int?) {
  for a in arr where a%2==0, b in opt {
    doSomething(a, b)
  }
}

(Of course, there is no reason why this syntax should work only on Optionals and Arrays; technically, it would apply to anything that's a monad, which, given a well-crafted library, would allow it to be used on, for example, values representing deferred computations (i.e., futures/promises).)

The missing piece

Eagle-eyed readers will have noticed a particular ugliness in the examples above: the inner block in the loop is hideously imperative; in each example, it either calls a hypothetical function (presumably with a side-effect) or printlns the output to the console. This is because Swift's if let and for...in are imperative statements, rather than functional expressions.

Of course, one can do things in a purely functional way if one dispenses with the pseudo-comprehension syntactic sugar of if let and/or for..in and rewrites everything in terms of map, filter and flatMap:

let arr:[Int] = ...
let opt: Int? = ...
  
let result = arr.flatMap { (a:Int)->Int? in 
  return opt.map { (b:Int)->Int in return a+b } 
}

It would be nice to not have to do this; to have a comprehension-style syntax which would let us specify the arrays and options we wish to go through and would assemble the result for us; perhaps it would look something like:

let result = for a in arr, b in opt { 
  return a+b 
}

(The use of the keyword return may be somewhat contentious, as (especially coming from a C background), one might expect it to return from whatever function contains the enclosing code. An alternative, as used in both Python and Scala, may be the keyword yield. But for now, in the interests of diverging as little as possible from Swift as it is, let's stick to return.)

Statements as expressions

Of course, by the time we have gotten here, we not only have functional for-comprehensions à la Scala, but we have, along the way, violated the implicit taboo against turning statements (such as for) into value-returning expressions. Once this is permissible, a wealth of possibilities are opened up. The pattern-matching switch statement, for example, starts to feel more powerful:

let description = switch(object) {
  case .User(let name): return "User '\(name)'"
  case .Group(let name, let members): return "Group '\(name) with \(members.count) members'"
}

Specifically, this would let us get rid of one ugly wart in the Swift language, i.e., initially undefined immutable values of this sort:

let description:String
switch(object) {
  case .User(let name): description = "User '\(name)'"
  case .Group(let name, let members): description = "Group '\(name) with \(members.count) members'"
}

An ugly workaround which, whilst comprehensible on a small scale, has the potential to become unwieldy.

Meanwhile, the C-style for loop could be pressed into service to generate arrays (or sequence types of some sort) on demand:

let squares: [Int] = for var i = 1; i < 5; i += 1 { 
  return i*i 
}

(Were one to omit the [Int] in the return value declaration, the result could be a lazy generator, which would evaluate the loop once for each value that is desired, which would lend itself to all kinds of lazy functional algorithms.)

And, of course, there is if, which would be merely a more verbose of the old C ? : operator:

let v1 = if a == 1 then "foo" else "bar"
let v2 = (a==1) ? "foo" : "bar"

In short, Swift as it stands (at version 1.2) is a few pieces of syntactic sugar away from being a much more elegant (and functional) language.

There are 2 comments on "Towards a more functional Swift":

Posted by: Kareman
Nottoobadsoftware.Com
Fri May 15 16:11:54 2015

Very good. It would also be helpful to have initializers be treated as functions, so they can be passed around and curried. Auto-currying would also be nice if possible.

Posted by: taritessier

Fri Jan 15 14:46:58 2016

Nice piece . Incidentally , if people is looking for a service to merge PDF files , my business discovered a tool here "http://www.altomerge.com/"

Want to say something? Do so here.

Post pseudonymously

Display name:
URL:(optional)
To prove that you are not a bot,
please enter the text in the image on the right
in the field below it.

Your Comment:

Please keep comments on topic and to the point. Inappropriate comments may be deleted.

Note that markup is stripped from comments; URLs will be automatically converted into links.