14

Shouldn't this work?

> val setOfSets = Set[Set[String]]()    
setOfSets: scala.collection.immutable.Set[Set[String]] = Set()

> setOfSets reduce (_ union _)
java.lang.UnsupportedOperationException: empty.reduceLeft
  at scala.collection.TraversableOnce$class.reduceLeft(TraversableOnce.scala:152)
  [...]
gladed
  • 1,705
  • 1
  • 17
  • 25
  • reduce is a special case of fold. You probably want to foldLeft starting from the empty set. – luqui Aug 08 '11 at 17:46
  • That's true. But what if I was doing `(_ intersect _)`? – gladed Aug 08 '11 at 18:54
  • 3
    @gladed: What would you expect the return value to be in that case? In general, what should `reduce`/`reduceLeft`/`reduceRight` return when given an empty collection? The function they're reducing with has type `A => B => B` for a collection `C[A]`, so they have no way to produce a value of type `B` out of thin air. – Antal Spector-Zabusky Aug 08 '11 at 19:46
  • 1
    The intent is "give me a set of only those strings that appear in all of the sets you have". If the set of source sets is empty, one would expect the result to be empty. Because of how `reduce` works, the special case of an empty set has to be handled with a conditional to supply the missing type `B`. I was hoping for something more elegant. – gladed Aug 08 '11 at 22:01
  • @gladed: "The result [being] empty" is not a generic notion for an arbitrary `B`. However, you should check out [Scalaz's `Foldable` trait](http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0.1/doc/scalaz/Foldable.html); in particular, look at `foldMap[A,M](t: F[A], f: A => M)(implicit m: Monoid[M]): M`. A [monoid](http://scalaz.github.com/scalaz/scalaz-2.9.0-1-6.0.1/doc/scalaz/Monoid.html) is simply a type with an associative binary operation (e.g. `++` for lists, `union` for sets, `*` for numbers, …) and an identity for that operation (`List()`, `Set()`, `1`). – Antal Spector-Zabusky Aug 09 '11 at 15:37
  • <...continued> Using `foldMap` lets you specify how to turn elements of your collection into elements of a monoid, and then implicitly combines them using the binary operation and the zero case. This works because *for monoids*, "being empty" *is* a generic notion (and so is "combining two objects"). (In other words, `t.foldMap(f)` becomes `t.fold((a,m) => f(a) |+| m)(mzero[M])`, where `|+|` is the binary operator and `mzero` is the identity.) – Antal Spector-Zabusky Aug 09 '11 at 15:38

3 Answers3

23

Reduce (left and right) cannot be applied on an empty collection.

Conceptually:

myCollection.reduce(f)

is similar to:

myCollection.tail.fold( myCollection.head )( f )

Thus the collection must have at least one element.

paradigmatic
  • 40,153
  • 18
  • 88
  • 147
  • But I didn't use `reduceLeft` or `reduceRight`; I used `reduce`, which does not specify an ordering. – gladed Aug 08 '11 at 18:45
  • 3
    Anyway I understand that reduce is fundamentally incompatible with the idea of an empty list. And I see the limitation in the scaladoc now. It's buried in the "returns" section: "The result of applying reduce operator op between all the elements _if the collection is nonempty._" I wish that had been stated upfront instead. – gladed Aug 08 '11 at 18:58
  • Why is the ordering important? – soc Aug 09 '11 at 13:46
  • Ordering is not important to understand the problem. Sets are not ordered. – paradigmatic Aug 09 '11 at 14:08
  • 4
    IMHO, Java8 got this right by making `reduce` return an `Optional` instead of the actual result. So if the collection is empty, you just get back a `None` instead of a runtime exception. – Sanjay T. Sharma Jul 12 '14 at 22:37
  • True, you can do that manually if you want to use reduce, i.e. pattern match on list first. – Nader Ghanbari Oct 25 '14 at 07:59
  • Feels like reduce is flawed. As Sanjay said should return an Option if there are special cases that need to be handled. Lesson learnt is to just avoid reduce and use fold. – Lionel Port Jan 29 '15 at 10:58
  • 2
    @LionelPort You are right, but `reduceOption` exists also. – paradigmatic Jan 29 '15 at 11:56
15

This should do what you want:

setOfSets.foldLeft(Set[String]())(_ union _)

Although I haven't understood the requirement to not specify an ordering.

soc
  • 27,983
  • 20
  • 111
  • 215
  • 10
    Suggest you use Set.empty[String] over Set[String](), there is no need to create a new instance of the empty set. – samthebest Nov 26 '13 at 17:33
9

Starting Scala 2.9, most collections are now provided with the reduceOption function (as an equivalent to reduce) which supports the case of empty sequences by returning an Option of the result:

Set[Set[String]]().reduceOption(_ union _)
// Option[Set[String]] = None
Set[Set[String]]().reduceOption(_ union _).getOrElse(Set())
// Set[String] = Set()
Set(Set(1, 2, 3), Set(2, 3, 4), Set(5)).reduceOption(_ union _).getOrElse(Set())
// Set[Int] = Set(5, 1, 2, 3, 4)
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190