6

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?

3 Answers 3

4

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)
Sign up to request clarification or add additional context in comments.

9 Comments

actually it's not purely functional, because f and s both take external state into account, so both of them yield different results depending on the value of doFilter and doSort. If you want it to be purely functional, you have to add a curried parameter to both of them, stating whether to use the function itself, or identity.
@drexin That can be purely functional. It's called a closure. If doFilter and doSort are mutable vars, then no, this isn't functional. But if they're immutable and depend only on global constants or immutable arguments to some outer scope, then this is indeed a functional approach, which you will find quite commonly used in languages such as Haskell that don't allow impure definitions.
Okay, sorry. This might, or might not be pure. But from the posting itself, it is not clear, where doFilter and doSort come from. As long as this is the case, I would consider this potentially impure.
@drexin Look at what I have added. Making the functions curried actually changes nothing at all about purity of code.
Yes it does. If doSort for example was a var, s would not be referentially transparent, sc is.
|
1

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

Comments

1

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))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.