Type 1.
into irb
and hit tab, and it’ll ask you if you want to see all 105 possibilities. Try it with [ ]
and you’ll be offered 155. Ruby comes with a lot of built-in methods on its default types.
It’s debatable how useful all of these are. The idiom in Swift seems much more to be like in the C++ STL, where there are algorithms that operate on collections, rather than collections having methods that operate on themselves. To see more about this, read this article on writing algorithms against collections.
But for the sake of looking at a few Swift language features, let’s implement some of the Ruby popular ones. Maybe they’d be useful making a Ruby refugee feel at home.
First off, the Int loops. Lots of “hey look, ruby is cool” intros kick off with this one:
10.times do |i| "ruby is cool!" end
This is pretty easy to add to the Swift Int using an extension:
extension Int { func times(f: () -> () ) { for _ in 0..self { f() } } } 10.times { "swift is cool too!" }
Note the _ instead of a variable in the for, because the value isn’t actually needed.
Next, two for
alternatives, upto
and downto
:
1.upto(3) {|i| print "#{i}.. "} # prints 1.. 2.. 3.. 3.downto(1) {|i| print "#{i}.. "} # prints 3.. 2.. 1..
in Swift would be:
extension Int { func upto(n: Int, f: (Int) -> () ) { for i in self...n { f(i) } } func downto(n: Int, f: (Int) -> () ) { for i in reverse(n...self) { f(i) } } }
Note the use of the inclusive ...
this time, unlike with times
which used the convention of a zero base up to but not including n
so needed non-inclusive ..
, because this time we want to start at self
and count inclusively to n
. Humorously, Ruby also has inclusive and exclusive ranges but ..
and ...
are the opposite way around.
For downto
, the range is wrapped in a reverse
in order to count backwards.
Next, some extensions to array, in a similar vein to map
or reduce
.
Ruby’s each
is like map
, but doesn’t generate any return value:
extension Array { func each(f: (Element) -> ()) { for e in self { f(e) } } }
Element
is a Type Property representing the type of value the Array contains, to this function works generically for type contained by array.
Ruby’s reverse_each
does the same but in the opposite direction:
extension Array { func each(f: (Element) -> ()) { for e in Swift.reverse(self) { f(e) } } }
Again, we use reverse
to go backwards through the array. Wait, you might think, isn’t it inefficient to reverse the array just to iterate over it? But that’s not what reverse
does – instead it generates a lazy collection that serves up the values in reverse order.
Note, we have to prefix it Swift.reverse
to disambiguate it from the Array
member function reverse
– which would result in creating a whole new array in reversed order, if we used it.
Ruby’s delete_if
is the opposite of Swift’s filter
– it takes a discriminator function and returns any elements that don’t match. We could easily just implement it from scratch with a for loop, but instead let’s try and implement it by re-using filter
.
One way to do this is to first create what’s called a “decorator”. Decorators are functions that take other functions, and return a new function that has changed the behaviour of the original function in some useful way.
What we want is a decorator that takes a function that returns a boolean, and then returns the opposite of that. Here it is:
// function that takes a function, f, that takes an element // and returns a bool, and returns a function that applies f // then returns the opposite func not_f<T>(f: (T)->Bool) -> (T)->Bool { // this returns a closure that captures f return { !f($0) } }
If you’re a bit hazy on closures and variable capture, read this.
Armed with not_f
, we can define delete_if
:
extension Array { func delete_if(f: (Element) -> Bool) -> Element[] { return self.filter(not_f(f)) } }
Finally, Ruby’s equivalent of filter
is called select
. It’s identical, it’s just called by a different name. Is there some way we could alias filter
if we wanted?
Yup, like this:
extension Array { var select: ((Element) -> Bool) -> Element[] { return Array.filter(self) } }
You can now use select
in exactly the same way you use filter
. This is quite a silly thing to do, but it does demonstrate a couple of language features: read-only computed properties that return closures, and references to member functions.
Read-only computed properties are used like regular properties i.e. can be accessed using the dot syntax, but behave like functions that take no arguments and return a value. In this case we have defined a property select that returns a function when you call it. We’re just returning an existing member function (more on that next) but you could have more complex logic that created and returned different kinds of closures.
The reason it’s nice to implement these as computed properties is you don’t need to use a ()
after the property name. This means if you want to compute a function for someone to use, they can use it like this: x.computedfun()
. If computedfun
was itself a function, you’d have to write this: x.computedfun()()
, which looks odd. Other than that, they behave just like functions that return functions.
So what is select
actually returning? To understand that, you need need to realize you can reference a classes’ member functions. See the following code:
class MyClass { let n: Int init(_ i: Int) { n = i } func multiply(i: Int) { println("n: \(n) * i: \(i) = \(n*i)") } func add(i: Int) { println("n: \(n) + i: \(i) = \(n*i)") } } let a = MyClass(2) let b = MyClass(5) // prints "n: 2 * i: 10 = 20" a.multiply(10) // prints "n: 5 + i: 10 = 50" b.add(10) // this takes a reference to the member // function multiply of MyClass var memfunref = MyClass.multiply // you can't use memfunref directly. // you have to apply an instance of // MyClass to it: var instancefunref = memfunref(a) // now you can call it, and it's // like calling a.multiply, so // this prints "n: 2 * i: 10 = 20" instancefunref(10) // MyClass.add has the same type as // MyClass.multiply, they're both (Int)->(), // so you can assign it to memfunref too: memfunref = MyClass.add // and apply a different instance of MyClass instancefunref = memfunref(b) // this prints "n: 5 + i: 10 = 50" instancefunref(10) // or you can skip the instancefunref step // prints "n: 5 + i: 10 = 50" again memfunref(b)(10)
Now we can see what that computed property is doing. It’s returning the member function filter
of Array
, then applying self
to it, and returning that. The caller then can call the returned function ()
and it runs filter
against the object they fetched it from. Like I said, bit silly, but it demonstrates a feature that could probably be put to better use in other cases.
If you liked this post, this one is also about implementing Ruby features in Swift, in this case implementing the ||=
operator using the @auto_closure
Swift feature.