[ ].blue

Reproducing Ruby's times in Scala

September 28, 2014

Continuing the theme I started yesterday of implementing other language features in Scala, one of the things I miss from Ruby is #times. Scala actually has a similar feature, Range. You can create ranges with the to or until methods...

1
2
3
4
5
scala> 1 to 5
res0: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

scala> 4 until 20 by 2
res1: scala.collection.immutable.Range = Range(4, 6, 8, 10, 12, 14, 16, 18)

At first glance it might seem like this is a bit of magic. What's actually happening is an implicit class is being applied. Consider:

1
2
3
4
implicit class RangeFactory(i: Int) {
  def to(j: Int) = new Range(i, j, 1)
  def until(j: Int) = new Range(i, j - 1, 1)
}

The by keyword is implemented on the Range class itself.

Something neat is that Range actually implements IndexedSeq, making it a true collection. As such it's possible to do things like foreach and map off of it:

1
2
scala> (1 to 5).map(_ + 1)
res2: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4, 5, 6)

As you can see Range is quite flexible and expressive. When we consider Ruby's #times behavior in Scala terms, we're really just talking about and implicit method that goes from 1 to the given Int then calls map:

1
2
3
implicit class RangeFactory(i: Int) {
  def times[A](f: (Int) => A) = new Range(1, i, 1).map(f)
}

That's it.

1
2
scala> 10.times { _ * 2 }
res3: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12, 14, 16, 18)