Ben Scheirman

Menu

Ruby Style Iteration in Swift

It’s no secret I’m a fan of Ruby. Idiomatic Ruby iteration looks like this:

1
2
3
5.times do |i|
  puts "Iteration ##{i+1}"
end

This outputs:

Iteration #1
Iteration #2
Iteration #3
Iteration #4
Iteration #5

You can also iterate over collections in this way:

1
2
3
["apples", "bananas", "cherries"].each do |item|
  puts "Eating #{item}"
end

And of course the output is this:

Eating apples
Eating bananas
Eating cherries

In Swift, your main workhorse for iteration is the for loop. Using the same examples above, it would look like this in Swift:

1
2
3
for i in 0..5 {
  println("Iteration #\(i)")
}

And the collection version…

1
2
3
for item in ["apples", "bananas", "cherries"] {
  println("Eating \(item)")
}

This is pretty clean syntax and there’s nothing wrong with it. But in my quest to explore the Swift language I thought it would be interesting to implement Ruby’s style of collection-first iteration. As it turns out, it’s simple.

Let’s start with the .times implementation. For this we have to extend the Int struct. We’ll start with the simpler example of not yielding the argument i to the block each time (we’ll add it later).

1
2
3
4
5
6
7
extension Int {
  func times(block: ()->()) {
    for _ in 0..self {
      block()
    }
  }
}

With that in place, we can do this:

1
3.times { print("Ho, ") }

That’s pretty clean! Let’s implement the variant that yields i each time through the block. For this we have to define another function with the same name (but different signature):

1
2
3
4
5
6
7
8
9
10
extension Int {
   func times...

   func times(block: (Int) -> ()) -> Int {
     for i in 0..self {
       block(i)
     }
     return self
   }
}

Note that this version also returns self, so you can assign the result of the iteration to a variable, if you wanted.

Usage here is slightly different:

1
5.times { (i: Int) in println("Iteration \(i)") }

Still pretty nice, and for simple loops, I give this a :thumbsup:. The downside here is that you have to specify the type of i otherwise you get an ambiguous function error from the compiler, because it can’t tell which version of the times method we’re referring to.

What about collections?

Using the same technique, we should be able to provide similar functionality to Arrays:

1
2
3
4
5
6
7
8
extension Array {
  func each(block: T -> ()) -> Array<T> {
    for item in self {
      block(item)
    }
    return self
  }
}

Note that the T type parameter comes from the initial Array definition.

With that we can iterate over items like this:

1
2
3
["Lannister", "Stark", "Greyjoy", "Tyrell", "Baratheon"].each { house in
  println("House \(house)")
}

Pretty simple. But Array is not the only thing we can enumerate. What about other types that you can use in a for loop, such as… Dictionary?

Dictionaries have keys and values, so we’ll iterate over the pair…

1
2
3
4
5
6
7
8
extension Dictionary {
    func eachPair(block: (KeyType, ValueType) -> ()) -> Dictionary<KeyType, ValueType> {
        for (key, val) in self {
            block(key, val)
        }
        return self
    }
}

Note here that KeyType and ValueType come from the initial declaration of Dictionary. This is important because KeyType actually has a constraint on it that enforces that it complies to the Hashable protocol.

With this you can iterate over dictionaries like this:

1
2
3
4
5
let mealPlan = [ "breakfast": "pancakes", "lunch": "burrito", "dinner": "steak"]

mealPlan.eachPair {
    meal, item in println("Having \(val) for \(key)")
}

Should you use this?

This is all well and good, but should you use it? This post is more of an exercise of what can we accomplish with Swift. Especially in the case of the dictionary, I actually prefer the for loop syntax:

1
2
3
for (meal, item) in mealPlan {
  ...
}

So I think these are best left as an exercise in practice, not necessarily a recommendation to make Swift behave just like Ruby. If you want that, RubyMotion is a fine choice.

Comments