I want to sort and/or filter a sequence. Basically something like this:
var result = getReallyLongSeq() // returns Seq[SomeClass]
if (doFilter) {
result = result.filter( ... )
}
if (doSort) {
result = result.sortWith( ... )
}
Now, this is an obviously valid approach, but is there a more functional way of doing that?
Whether it's more readable or not is open to debate. It is a bit inefficient but it's also purely functional. This approach is also easily extended and rather maintainable.
val f: Seq[SomeClass] => Seq[SomeClass] = if(doFilter) _.filter(...) else identity
val s: Seq[SomeClass] => Seq[SomeClass] = if(doSort) _.sortWith(...) else identity
(s compose f)(result)
You can also write the following, which is more like the code in the OP. It's also slightly more efficient (but less generic).
val filtered = if(doFilter) result.filter(...) else result
if(doSort) filtered.sortWith(...) else filtered
If for some reasons you prefer to use curried functions for the first example (as mentioned in the comments), you can write the following:
def fc(df: Boolean)(xs: Seq[SomeClass]) = if(df) _.filter(...) else identity
def sc(ds: Boolean)(xs: Seq[SomeClass]) = if(ds) _.sortWith(...) else identity
(sc(doSort) compose fc(doFilter))(result)
But then you might further write it like this and end up in nearly the same thing as given in the first example:
def fc(df: Boolean)(xs: Seq[SomeClass]) = if(df) _.filter(...) else identity
def sc(ds: Boolean)(xs: Seq[SomeClass]) = if(ds) _.sortWith(...) else identity
val f = fc(doFilter)
val s = sc(doSort)
(s compose f)(result)
Without library support, you can roll out your own Boolean Reader Monad.
This is functional, pure and configurable.
Boolean Reader
case class BoolConf[A](run: Boolean => A) {
def apply(b: Boolean) = run(b)
def map[B](f: A => B): BoolConf[B] = BoolConf(b => f(run(b)))
def flatMap[B](f: A => BoolConf[B]): BoolConf[B] = BoolConf(b => f(run(b))(b))
}
Here we made a wrapper for a Boolean => A
that allows monadic composition, and it's probably already implemented in some library, like scalaz.
We're only interested in the run
method, for this case, but you can get interested in other opportunities.
Configured filter & sort
Then we wrap our filter and sort check with the Reader
val mFilter: Seq[SomeClass] => BoolConf[Seq[SomeClass]] = seq => BoolConf(if(_) seq.filter(...) else seq)
val mSort: Seq[SomeClass] => BoolConf[Seq[SomeClass]] = seq => BoolConf(if(_) seq.sortWith(...) else seq)
Lifting
Now, to compose these functions, since the output is no more a simple Seq
, we need to lift one of them to work within a BoolConf
def lift2Bool[A, B]: (A => B) => (BoolConf[A] => BoolConf[B]) =
fun => cfg => BoolConf(bool => fun(cfg(bool)))
Now we're able to convert any function from A => B
to a lifted function from BoolConf[A] => BoolConf[B]
Composing
Now we can compose functionally:
val filterAndSort = lift2Bool(mSort) compose mFilter
//or the equivalent
val filterAndSort = mFilter andThen lift2Bool(mSort)
//applies as in filterAndSort(<sequence>)(<do filter>)(<do sort>)
There's more
We can also create a generic "builder" for our mFilter
and mSort
val configFilter[SomeClass]: (SomeClass => Boolean) => Seq[MyClass] => BoolConf[Seq[SomeClass]] =
filterer => seq => BoolConf(if(_) seq.filter(filterer))
You can "sort" the sorting equivalent by yourself
Thanks are due to Runar for the inspiration
You can use scalaz oparator |>
or define your own:
class PipedObject[T](value: T)
{
def |>[R](f: T => R) = f(value)
}
implicit def toPiped[T](value: T) = new PipedObject[T](value)
(result |> (r => if (doFilter) r.filter(...) else r)
|> (r => if (doSort) r.sortWith(...) else r))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With