Using Any to Store Numbers in Swift

In the last post we implemented an accumulator using generics, so it could handle either integers or floating point types.

But how about handling both in the same accumulator? In Paul Graham’s guidelines for submitting an accumulator, he says it needs to work

for any numeric type – i.e. can take both ints and floats and returns functions that can take both ints and floats. (It is not enough simply to convert all input to floats. An accumulator that has only seen integers must return integers.)

He then gives some example usage: 1

E.g. if after the example, you added the following code (in a made-up language):
x = foo(1);
x(5);
foo(3);
print x(2.3)
It should print 8.3.

But if you try to do this with our generics implementation, you will get a error when you pass in the final number. “Cannot convert the expression’s type ‘Int’ to type ‘Int’”, the compiler will say, which is a little confusingly worded given it’s a Double that can’t be converted, but what it means is it can’t convert what you passed to it’s Int parameter into an Int.

So I guess our example still isn’t valid for submission. What behaviour do we need? In Ruby, you can do the following:

irb> n = 1
 => 1
2.1.0 :002 > n.class
 => Fixnum
2.1.0 :003 > n += 2.2
 => 3.2
2.1.0 :005 > n.class
 => Float

The value n silently gets upgraded to a float. How can we do this in Swift? Well, there is an Any type that can hold any kind of type. But it’s not as easy as that. Just trying the following:

  func foo(var n: Any) -> (Any) -> Any {
    return { n += $0; return n }
  }

gets you the familiar compiler error about there being no overloaded += operator. But unlike when we had this problem with generics, this can’t be fixed with type constraints. Any doesn’t implement +=.2 In fact, Any doesn’t let you do much of anything except store a value and pass it around. If you want to maniuplate what’s inside, you must use a switch statement:

let a: Any = 1
switch a {
case let i as Int:
  // do appropriate logic when it's an Int
...
}

If we revisit our incf implementation, but adapt it to take and return Any, and upgrade an Int to Double by switching to identify them, we get the following monstrocity:

func incf(inout lhs: Any, rhs: Any) -> Any {
    switch lhs {
    case let i as Int:
        switch rhs {
        case let j as Int:
            lhs = i + j
        case let d as Double:
            lhs = Double(i) + d
        default:
            // what to do here?
        }
    case let d as Double:
        switch rhs {
        case let e as Double:
            lhs = d + e
        case let i as Int:
            lhs = d + Double(i)
        default:
            // what to do here?
        }
    default:
            // what to do here?
    }
    return lhs
}

What to put in for those default cases, where one of the types isn’t supported. Maybe throw an exception? Hahaha only kidding, Swift doesn’t have exceptions.3 The Swift way of handling this would be to allow incf to return a nil type.4 Users of foo then have to account for the possibility of an error. Let’s do that, and add an implementation of foo with our new incf:

func incf(inout lhs: Any, rhs: Any) -> Any? {
    switch lhs {
    case let i as Int:
        switch rhs {
        case let j as Int:
            lhs = i + j
        case let d as Double:
            lhs = Double(i) + d
        default:
            lhs = nil
        }
    case let d as Double:
        switch rhs {
        case let e as Double:
            lhs = d + e
        case let i as Int:
            lhs = d + Double(i)
        default:
            lhs = nil
        }
    default:
        lhs = nil
    }
    return lhs
}

func foo(var n: Any) -> (Any) -> Any? {
    return { incf(&n, $0) }
}

There are many flaws in this approach, but it does at least work with the example above now – pass an Int and then a Double and you’ll get a Double.

Pass a Float in though and you get back a nil. But adding Float to the already nasty switch statement will make it even nastier. If we wanted to extend incf to cover even more types (maybe numeric classes like a Rational or Decimal) it gets even worse. And if we want to implement a decrement function we’d have to replicate the whole statement.

There’s one more way to write the switch statement. Swift’s pattern matching can operate on multiple values together, giving us the following alternative to having to nest multiple switch statements:

func incf(inout lhs: Any, rhs: Any) -> Any? {
    switch (lhs,rhs) {
    case let (i as Int, j as Int):
        lhs = i + j
    case let (d as Double, e as Double):
        lhs = d + e
    case let (i as Int, d as Double):
        lhs = Double(i) + d
    case let (d as Double, i as Int):
        lhs = Double(i) + d
    default:
        lhs = nil
    }
    return lhs
}

Unfortunately, while this code is a little more succinct, and possibly less error-prone, it still suffers from the same problems if you tried to extend it to more types.

The next post will be about ditching Any in favor of a more type-safe approach to this problem.


  1. Note, he calls foo a second time in his example. Presumably he got a lot of duff submissions where people were using global variables to accumulate. 
  2. And before you try it, no, you can’t extend Any to try and implement +=. Or anything else for that matter, it’s unextendable. 
  3. This is a good example of how removing exceptions forces you to find a perfectly valid alternative in many cases. Exceptions were always discouraged in Objective-C except for truly exceptional circumstances, Swift took this one step further and did away with them entirely. 
  4. If you think the right thing to do is not add the number, you’ll hit a different problem. That means the default action should do nothing, but Swift insists you have some code there. So you have to write something that doesn’t have any effect. Feel free to get creative. No, I’m afraid a comment saying “do nothing” doesn’t count as something. 

An Accumulator in Swift, Part 2 – Using Generics

In Part 1 of this post, we wrote an implementation of Paul Graham’s accumulator problem in Swift. Here’s one of the versions:

func foo(var n: Int) -> (Int) -> Int {
  return { n += $0; return n }
}

This looks pretty similar to the Ruby version he gives:

def foo (n)
  lambda {|i| n += i }
end

Except there’s a problem. On his page of guidelines for submitting an example in a new language1 he points out it needs to:

[work] for any numeric type – i.e. can take both ints and floats and returns functions that can take both ints and floats. (It is not enough simply to convert all input to floats. An accumulator that has only seen integers must return integers.)2

In Ruby, this isn’t an issue because of the duck typing. But in Swift, if you try to pass a Float to foo, you’ll get an error. We’re going to have to use another feature to fix this – generics.3

First let’s try implementing incf using generics, so it can take an Int or a Float. We do this by defining a type parameter T in angle brackets after the function name, and then using that instead of the explicit Ints:

func foo<T>(var n: T) -> (T) -> T {
  return { n += $0; return n }
}

Oops, more compiler errors. “Could not find an overload for ‘+=’ that accepts the supplied arguments” again.

This is because not all objects have a += operator, so if you tried to use it with one of those, it wouldn’t work. Unlike in C++ templates, where you wouldn’t get a compiler error4 unless you tried to use incf with a type that was missing a +=, Swift requires you to restrict your types up-front.

The way you ensure a type will support the necessary member functions is by using Type Constraints. These are protocols you append to your generic type. For example, if you wanted to write a generic less_than function, you could use the Comparable type:

func less_than<T: Comparable>(lhs: T, rhs: T) -> Bool {
    return lhs < rhs
}

Only classes that implement the Comparable protocol can be used with the less_than function, and because they impelement this protocol they’re guaranteed to have a < operator.

The Swift standard library only defines 3 protols so far – Equatable, Comparable and Printable.5 They don't cover what we need, so we'll define our own. Let's call it, uhmm, Addable.

protocol Addable {
    @assignment func += (inout left: Self, right: Self)
}

Then, we need to declare that Int and Float support Addable by extending them with the new protocol:

  extension Int: Addable { }
  extension Float: Addable { }

Finally, let's try our generic function with the new constraint:

func foo<T: Addable>(var n: T) -> (T) -> T {
  return { n += $0; return n }
}

Now, if you pass in a Float to foo, it works.6. In fact, if you tag any type that implements += it will work for that type too. Try it with String.

Anyway, with this requirement covered, hopefully we can add Swift to the pantheon of powerful languages. Maybe one day they'll take new submissions to the page!


  1. These pages tell a story. First he was all hey guys, let’s get all the examples, this is cool! Then he’s like, no you numbskulls, read the problem, that’s not what it says! Eventually he gives up and tells people to stop emailing him. 
  2. Strictly speaking what we’ll implement still doesn’t cover this requirement, because the quote and the example following it implies it needs to be able to switch dynamically between integers and floating point halfway through. Read the next part for a solution to this. 
  3. I love generics. But then the first language they taught me in college was Ada, and after that I spent years in the C++ mines, so it’s not surprising. 
  4. The compiler error will be about 10 lines long and will fill you with hopeless despair. Concepts, something similar to Swift’s protocol constraints, didn’t make it into C++11. 
  5. They don't define Assignable. I guess that's considered so fundamental you can take it as a given. Presumably they're going to add a lot more standard protocols to the library over time, so keep an eye on that page. There are several more implemented but not yet documented – to see them, type import Swift into playground, and command-click it. 
  6. I was pretty amazed when it did the first time I tried this.