Question

I want to write a chain of transformations and finally have a block where I do something with the resulting collection, without using variables. Is there a higher-order function on collections that just provides the collection on which it is called to the supplied function?

Query( ... )
  .map { ... }
  .filter { ... }
  .combinations(2)
  .pipeTo { col =>
     ...
     consolidate(col)
  }

Of course for a single function call one could do it like this:

consolidate(
  Query()
    .map { ... }
    .filter { ... }
    .combinations(2)
)

But the former reads more natural to me and allows e.g. for an easy injection of some conditionals.

A workaround I've been using is placing a match clause at the end:

Query()
  .map { ... }
  .filter { ... }
  .combinations(2)
  match { case lst =>
    ...
    consolidate(lst)
  }

But since this pattern occurs quite often I was wondering what is the idiomatic way to write this.

Was it helpful?

Solution

Perhaps the most idiomatic way would be to use the so called Pimp My Library Pattern, which allows one to add extension methods to any classes via implicit class conversions.

For example, to add a consolidate method to Lists, you could write:

object ListExtensions {
  implicit class ConsolidatableList[T](val lst: List[T]) extends AnyVal {
    def consolidate = ??? // Do whatever you want with the List lst
  }
}

import ListExtensions._

List(1,2,3)
  .map(x => x * x)
  .consolidate

If you really want to be able to call arbitrary functions on arbitrary objects in a chain, you could even define an "pipeline" operator like in F#:

object Extensions {
  implicit class PipelineExtension[T](val obj: T) extends AnyVal {
    def |>[S](f: T => S): S = f(obj)
  }
}

import Extensions._

List(1,2,3)
  .map(x => x * x) 
  .|> (consolidate)

Query( ... )
  .map { ... }
  .filter { ... }
  .combinations(2)
  .|> { col =>
     ...
     consolidate(col)
  }

The |> operator behaves like the pipeTo method you have defined.

Basically, this works by creating an implicit conversion from your type to the type with the various extension methods that you define. The AnyVal makes the extension class an value class, so its use will have little to no runtime cost.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top