Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional way of conditional filtering and sorting in scala

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?

like image 766
F0RR Avatar asked Oct 25 '12 07:10

F0RR


3 Answers

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

Discussion based on comments

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)
like image 154
ziggystar Avatar answered Sep 29 '22 17:09

ziggystar


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

like image 31
pagoda_5b Avatar answered Sep 29 '22 17:09

pagoda_5b


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))
like image 43
Sergey Passichenko Avatar answered Sep 29 '22 17:09

Sergey Passichenko