Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala functional programming gymnastics

I am trying to do the following in as little code as possible and as functionally as possible:

def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double

Obviously the following works:

= (floor -> cap) match {
    case (None, None)       => amt
    case (Some(f), None)    => f max amt 
    case (None, Some(c))     => c min amt
    case (Some(f), Some(c)) => (f max amt) min c
  }

I was really hoping for something more elegant and will accept use of the Scalaz library! You can assume that the following is true:

floor.forall( f => cap.forall( _ > f))

If anyone is interested, here is some test code:

object Comparisons {
  sealed trait Cf {
    def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double
  }

  def main(args: Array[String]) {
    val cf : Cf = //TODO - your impl here!
    def runtest(floor: Option[Double], cap: Option[Double], amt: Double, exp : Double) : Unit = {
      val ans = cf.restrict(floor, cap, amt)
      println("floor=%s, cap=%s, amt=%s === %s (%s) : %s".format(floor, cap, amt, ans, exp, if (ans == exp) "PASSED" else "FAILED"))
    }
    runtest(Some(3), Some(5), 2, 3)
    runtest(Some(3), Some(5), 3, 3)
    runtest(Some(3), Some(5), 4, 4)
    runtest(Some(3), Some(5), 5, 5)
    runtest(Some(3), Some(5), 6, 5)

    runtest(Some(3), None, 2, 3)
    runtest(Some(3), None, 3, 3)
    runtest(Some(3), None, 4, 4)
    runtest(Some(3), None, 5, 5)
    runtest(Some(3), None, 6, 6)

    runtest(None, Some(5), 2, 2)
    runtest(None, Some(5), 3, 3)
    runtest(None, Some(5), 4, 4)
    runtest(None, Some(5), 5, 5)
    runtest(None, Some(5), 6, 5)

    runtest(None, None, 2, 2)
    runtest(None, None, 3, 3)
    runtest(None, None, 4, 4)
    runtest(None, None, 5, 5)
    runtest(None, None, 6, 6)
  }
}
like image 699
oxbow_lakes Avatar asked Nov 10 '10 13:11

oxbow_lakes


2 Answers

Edit 2:

While thinking about the cataX method, I figured out that cataX is nothing else than a plain and simple fold. Using that, we can get a pure scala solution without any additional libraries.

So, here it is:

( (amt /: floor)(_ max _) /: cap)(_ min _)

which is the same as

cap.foldLeft( floor.foldLeft(amt)(_ max _) )(_ min _)

(not that this is necessarily easier to understand).

I think you can’t have it any shorter than that.


For better or worse, we can also solve it using scalaz:

floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m))

or even:

floor.cata(amt max, amt) |> (m => cap.cata(m min, m))

As a ‘normal’ Scala programmer, one might not know about the special Scalaz operators and methods used (|> and Option.cata). They work as follows:

value |> function translates to function(value) and thus amt |> (m => v fun m) is equal to v fun amt.

opt.cata(fun, v) translates to

opt match {
  case Some(value) => fun(value) 
  case None => v
}

or opt.map(fun).getOrElse(v).

See the Scalaz definitions for cata and |>.

A more symmetric solution would be:

amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m))

Edit: Sorry, it’s getting weird now, but I wanted to have a point-free version as well. The new cataX is curried. The first parameter takes a binary function; the second is a value.

class CataOption[T](o: Option[T]) {
  def cataX(fun: ((T, T) => T))(v: T) = o.cata(m => fun(m, v), v)
}
implicit def option2CataOption[T](o: Option[T]) = new CataOption[T](o)

If o matches Some we return the function with the value of o and the second parameter applied, if o matches None we only return the second parameter.

And here we go:

amt |> floor.cataX(_ max _) |> cap.cataX(_ min _)

Maybe they already have this in Scalaz…?

like image 164
Debilski Avatar answered Oct 17 '22 19:10

Debilski


Not quite as terse as the scalaz version, but on the other hand, no dependencies,

List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)
like image 35
Miles Sabin Avatar answered Oct 17 '22 21:10

Miles Sabin