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 Int
s:
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!
- 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. ↩
- 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. ↩
- 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. ↩
- 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. ↩
-
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. ↩ - I was pretty amazed when it did the first time I tried this. ↩
[…] point. Except… we didn’t quite solve the stated problem. To find out why, read on to Part 2 of this […]
[…] the last post we implemented an accumulator using generics, so it could handle either integers or floating point […]
Reblogged this on Dinesh Ram Kali..