Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

General comprehensions in Scala

As far as I understand, the Scala for-comprehension notation relies on the first generator to define how elements are to be combined. Namely, for (i <- list) yield i returns a list and for (i <- set) yield i returns a set.

I was wondering if there was a way to specify how elements are combined independently of the properties of the first generator. For instance, I would like to get "the set of all elements from a given list", or "the sum of all elements from a given set". The only way I have found is to first build a list or a set as prescribed by the for-comprehension notation, then apply a transformation function to it - building a useless data structure in the process.

What I have in mind is a general "algebraic" comprehension notation as it exists for instance in Ateji PX:

`+ { i | int i : set }               // the sum of all elements from a given set
set() { i | int i : list }           // the set of all elements from a given list
concat(",") { s | String s : list }  // string concatenation with a separator symbol

Here the first element (`+, set(), concat(",")) is a so-called "monoid" that defines how elements are combined, independently of the structure of the first generator (there can be multiple generators and filters, I just tried to keep the examples concise).

Any idea about how to achieve a similar result in Scala while keeping a nice and concise notation ? As far as I understand, the for-comprehension notation is hard-wired in the compiler and cannot be upgraded.

Thanks for your feedback.

like image 357
pato Avatar asked Dec 03 '22 07:12

pato


2 Answers

About the for comprehension

The for comprehension in scala is syntactic sugar for calls to flatMap, filter, map and foreach. In exactly the same way as calls to those methods, the type of the target collection leads to the type of the returned collection. That is:

list map f   //is a List
vector map f // is a Vector

This property is one of the underlying design goals of the scala collections library and would be seen as desirable in most situations.

Answering the question

You do not need to construct any intermediate collection of course:

(list.view map (_.prop)).toSet //uses list.view

(list.iterator map (_.prop)).toSet //uses iterator

(for { l <- list.view} yield l.prop).toSet //uses view

(Set.empty[Prop] /: coll) { _ + _.prop } //uses foldLeft

Will all yield Sets without generating unnecessary collections. My personal preference is for the first. In terms of idiomatic scala collection manipulation, each "collection" comes with these methods:

//Conversions
toSeq
toSet
toArray
toList
toIndexedSeq
iterator
toStream

//Strings
mkString

//accumulation
sum 

The last is used where the element type of a collection has an implicit Numeric instance in scope; such as:

Set(1, 2, 3, 4).sum //10
Set('a, 'b).sum //does not compile

Note that the String concatenation example in scala looks like:

list.mkString(",")

And in the scalaz FP library might look something like (which uses Monoid to sum Strings):

list.intercalate(",").asMA.sum

Your suggestions do not look anything like Scala; I'm not sure whether they are inspired by another language.

like image 130
oxbow_lakes Avatar answered Jan 11 '23 22:01

oxbow_lakes


foldLeft? That's what you're describing.

The sum of all elements from a given set:

(0 /: Set(1,2,3))(_ + _)

the set of all elements from a given list

(Set[Int]() /: List(1,2,3,2,1))((acc,x) => acc + x)

String concatenation with a separator symbol:

("" /: List("a", "b"))(_ + _) // (edit - ok concat a bit more verbose:
("" /: List("a", "b"))((acc,x) => acc + (if (acc == "") "" else ",")  + x)
like image 45
huynhjl Avatar answered Jan 12 '23 00:01

huynhjl