Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic Scala: Semantic difference between <Collection>Like and <Collection> Types?

As title of this question suggests: my question is more about form (idiomatic convention) than function. Put Succinctly:

What is the semantic difference between MyCollectionLike and MyCollection?

As examples: What is the difference between StringLike and String or MapLike and Map. Looking closely at the Scala API docs, I can tell that XLike is typically a super-type of X. But, beyond that, I am not clear on the semantic difference between these layers of abstraction.

In practice, If I were creating a new class/trait, understanding this distinction would be helpful when I am choosing names for said class.

My specific problem, where this has come up is as follows:

I want to create the trait: SurjectiveMap[K, T] which can be mixed-in with either Map[K, Set[T]] or MapLike[K, SetLike[T]]. Given that I do not know the semantic difference between *Like and *, I am not sure which to go with.

like image 335
Ryan Delucchi Avatar asked May 29 '13 21:05

Ryan Delucchi


2 Answers

The same difference as between IFoo and Foo, Bar and BarImpl (except the fact that TraversableLike is super-trait which contains implementation):

The Scala collection library avoids code duplication and achieves the "same-result-type" principle by using generic builders and traversals over collections in so-called implementation traits. These traits are named with a Like suffix; for instance, IndexedSeqLike is the implementation trait for IndexedSeq, and similarly, TraversableLike is the implementation trait for Traversable. Collection classes such as Traversable or IndexedSeq inherit all their concrete method implementations from these traits.

from Scala collections architecture

like image 124
om-nom-nom Avatar answered Nov 16 '22 22:11

om-nom-nom


I think using the Like traits lets you refine the return (representation) types. This involves a lot more work. Compare:

import collection.generic.CanBuildFrom

object FooMap {
  type Coll = FooMap[_, _]
  implicit def canBuildFrom[A, B]: CanBuildFrom[Coll, (A, B), FooMap[A, B]] = ???
}
trait FooMap[A, +B] extends Map[A, B] {
  def foo = 33
}

def test(f: FooMap[Any, Any]) {
  f.map(identity).foo  // nope, we ended up with a regular `Map`
}

versus

object FooMap extends collection.generic.ImmutableMapFactory[FooMap] {
  override type Coll = FooMap[_, _]
  implicit def canBuildFrom[A, B]: CanBuildFrom[Coll, (A, B), FooMap[A, B]] = ???
  def empty[A, B]: FooMap[A, B] = ???
}
trait FooMap[A, +B] extends Map[A, B] 
  with collection.immutable.MapLike[A, B, FooMap[A, B]] {

  def foo = 33
  override def empty: FooMap[A, B] = FooMap.empty[A, B]
}

def test(f: FooMap[Any, Any]) {
  f.map(identity).foo  // yes
}

The MapLike trait must be mixed in after the Map trait for the correct return types to kick in.


Still you don't get everything for free it seems, e.g. you will need to override more methods:

override def +[B1 >: B](kv: (A, B1)): FooMap[A, B1]  // etc.
like image 33
0__ Avatar answered Nov 16 '22 22:11

0__