4

I have realized that my typical way of passing Scala collections around could use some improvement.

def doSomethingCool(theFoos: List[Foo]) = { /* insert cool stuff here */ }

// if I happen to have a List
doSomethingCool(theFoos)

// but elsewhere I may have a Vector, Set, Option, ...
doSomethingCool(theFoos.toList)

I tend to write my library functions to take a List as the parameter type, but I'm certain that there's something more general I can put there to avoid all the occasional .toList calls I have in the application code. This is especially annoying since my doSomethingCool function typically only needs to call map, flatMap and filter, which are defined on all the collection types.

What are my options for that 'something more general'?

leedm777
  • 23,444
  • 10
  • 58
  • 87

2 Answers2

6

Here are more general traits, each of which extends the previous one:

  • GenTraversableOnce
  • GenTraversable
  • GenIterable
  • GenSeq

The traits above do not specify whether the collection is sequential or parallel. If your code requires that things be executed sequentially (typically, if your code has side effects of any kind), they are too general for it.

The following traits mandate sequential execution:

  • TraversableOnce
  • Traversable
  • Iterable
  • Seq
  • LinearSeq

The first one, TraversableOnce only allows you to call one method on the collection. After that, the collection has been "used". In exchange, it is general enough to accept iterators as well as collections.

Traversable is a pretty general collection that has most methods. There are some things it cannot do, however, in which case you need to go to Iterable.

All Iterable implement the iterator method, which allows you to get an Iterator for that collection. This gives it the capability for a few methods not present in Traversable.

A Seq[A] implements the function Int => A, which means you can access any element by its index. This is not guaranteed to be efficient, but it is a guarantee that each element has an index, and that you can make assertions about what that index is going to be. Contrast this with Map and Set, where you cannot tell what the index of an element is.

A LinearSeq is a Seq that provides fast head, tail, isEmpty and prepend. This is as close as you can get to a List without actually using a List explicitly.

Alternatively, you could have an IndexedSeq, which has fast indexed access (something List does not provide).

See also this question and this FAQ based on it.

Community
  • 1
  • 1
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
4

The most obvious one is to use Traversable as the most general trait which will have the goodies you want. However, I think you are generally better sticking to:

  • Seq
  • IndexedSeq
  • Set
  • Map

A Seq will cover List, Vector etc, IndexedSeq will cover Vector etc etc. I found myself not using Iterable because I often need (or want) to know the size of the thing I have and back pre scala-2.8 Iterable did not provide access to this, so I kept having to turn things into sequences anyway!

Looks like Traversable and Iterable now have size methods so maybe I should go back to using them! Of course you could start "going mad" with GenTraversableOnce but that is not likely to aid in readability.

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • Thanks! `Seq` does not cover `Option` or Lift's [`Box`](http://scala-tools.org/mvnsites/liftweb-2.3/net/liftweb/common/Box.html), which is usually what I have. I'll try out `Traversable` and `Iterable`. – leedm777 Dec 22 '11 at 15:28
  • 1
    Take care when using `Iterable`. If you get a `Set` and call `map` on it, equal elements of the map-result will get "merged". This might not be what you want to do. – ziggystar Dec 22 '11 at 15:49
  • Traversable and Iterable have always had size (well, since 2.8.0). – Daniel C. Sobral Dec 22 '11 at 19:31
  • Yes - but I didn't notice that `Traversable` had size; I got used to calling `toList` to get size – oxbow_lakes Dec 22 '11 at 23:45