Can the Scala collection of seq / par / view / force be considered a violation of the principle of uniform return?

Most of the complexity of implementing a collection structure arises from the fact that Scala can - unlike C # LINQ or other collection frameworks - return the "best" type of collection for higher-order functions:

val numbers = List(1,2,3,4,5) numbers map (2*) // returns a List[Int] = List(2, 4, 6, 8) val doubles = Array(1.0, 2.0, 3.0) doubles filter (_ < 3) // returns Array[Double] = Array(1.0, 2.0) 

Why is this principle not true for methods like seq , par , view , force ?

 numbers.view.map(2*).force // returns Seq[Int] = List(2, 4, 6, 8) numbers.seq // returns scala.collection.immutable.Seq[Int] = List(1, 2, 3, 4) doubles.par.seq // returns scala.collection.mutable.ArraySeq[Double] = ArraySeq(1.0, 2.0, 3.0) 

Are there any technical limitations that prevent him from working? Or is it design / intention? Given that LINQ is mostly lazy, the Scala equivalent ( view , force ) is no more type-safe (only when using strict methods), right?

+8
collections c # types scala linq
source share
2 answers

You can add more type information to the classes in the parallel collection so that you return the assembly you started with, that's true. This would mean that after turning a List into ParVector by calling par (in O (n), since the elements are copied to the vector) and then calling seq , you will get List again. To get the list using seq , you have to copy all the elements from the vector back to the list. Instead, the following happens:

  • ParVector converted back to Vector when seq is called - it is converted to O (1)
  • calling par again on this vector will give you the ParVector in O (1), since both vectors and parallel vectors have the same underlying data.

Please note that a collection, such as a list, must be restructured when converted to a parallel set, otherwise operations with it cannot be effectively parallelized.

This way, you donโ€™t have to pay for copying again when calling par and seq - conversions become much more efficient. Since the main goal of parallel collections was to increase efficiency, this was considered more important than the principle of uniform return.

+8
source share

Regarding the type of static return of the collection method, C # supports overloading LINQ extension methods ( Select , Where , etc.) and automatically selects the most specific scope. Therefore, Seq.Select can return Seq , Enumerable.Select can return Enumerable , etc. This is very similar to how the most specific implicit implementation is chosen in Scala.

As for the dynamic type, LINQ operations are implemented as extension methods, so dynamic dispatch is not performed. So (Seq as Enumerable).Select will return Enumerable , not Seq . In Scala, collection method calls are dynamically sent, so the dynamic type will remain unchanged for map , filter , etc. Both approaches have advantages / disadvantages. The only clean solution to this kind of problem is the ellipsis of IMHO, and neither language nor runtime support them effectively.

Regarding runtime behavior, LINQ always returns a lazily evaluated collection view. There is no method on the view that magically returns a collection of the original type, you need to manually specify what you want, for example, an array using the toArray method. IMHO, this is a cleaner and simpler approach than the one used by Scala, and lazy addition operations are better than strict ones, but this involves an additional method call to get a strict collection for single collection operations.

+1
source share

All Articles