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 : D
Not prettier, not much shorter, and certainly not faster! But it is more composable more generic and more "functional":
EDIT: made the code fully generic :)
def optWith[T](a: Option[T], b: T)(op:(T,T)=>T) =
a map (op(b,_)) getOrElse b
def optMin[T:Numeric](a: Option[T]) =
(b:T) => optWith(a, b)(implicitly[Numeric[T]].min)
def optMax[T:Numeric](a: Option[T]) =
(b:T) => optWith(a, b)(implicitly[Numeric[T]].max)
def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT])
(implicit ev:Numeric[T], fv:FT=>T, cv:CT=>T) =
optMin(ceil map cv) compose optMax(floor map fv) apply(x)
UPDATE 2: There's also this version, taking better advantage of Numeric
def optWith[T](a: Option[T])(op:(T,T)=>T) =
(b:T) => a map (op(b,_)) getOrElse b
def restrict[T,FT,CT](x:T, floor:Option[FT], ceil:Option[CT])
(implicit n:Numeric[T], fv:FT=>T, cv:CT=>T) =
optWith(ceil map cv)(n.min) compose optWith(floor map fv)(n.max) apply(x)
I hope you like type signatures :)
UPDATE 3: Here's one that does the same with bounds
def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) =
(b:T) => a map (op(b,_)) getOrElse b
def restrict[T:Numeric, FT <% T, CT <% T]
(floor:Option[FT], ceil:Option[CT], amt:T) = {
val n = implicitly[Numeric[T]]; import n._
optWith(min)(ceil) compose
optWith(max)(floor) apply(amt)
}
If nothing else... this shows quite clearly why import parameters would be a Good Thing(tm). Imagine if the following was valid code:
def optWith[T, V <% T](op:(T,T)=>T)(a: Option[V]) =
(b:T) => a map (op(b,_)) getOrElse b
def restrict[import T:Numeric,FT <% T,CT <% T]
(floor:Option[FT], ceil:Option[CT], amt:T) = {
optWith(min)(ceil) compose
optWith(max)(floor) apply(amt)
}
UPDATE 4: Turning the solution upside down here. This one offers some more interesting possibilities for future extension.
implicit def optRhs[T:Ordering](lhs:T) = new Object {
val ord = implicitly[Ordering[T]]; import ord._
private def opt(op: (T,T)=>T)(rhs:Option[T]) =
rhs map (op(lhs,_)) getOrElse lhs
def max = opt(ord.max) _
def min = opt(ord.min) _
}
def restrict[T : Ordering](floor:Option[T], cap:Option[T], amt:T) =
amt min cap max floor
With any luck, I'll inspire someone else to build a better solution out of mine. That's how these things usually work out...
This isn't really much easier in Scalaz than in regular Scala:
def restrict(floor: Option[Double], cap: Option[Double], amt: Double) =
floor.map(amt max).orElse(Some(amt)).map(x => cap.map(x min).getOrElse(x)).get
(Add _
after max
and min
if it makes you feel better to see where the parameter goes.)
Scalaz is a little easier to read, though, once you understand what the operators do.
I'm adding another answer which was inspired by both retronym and Debilski - basically it amounts to converting the cap and floor to functions (Double => Double
, if they are present) and then folding the identity function through them with composition:
def restrict(floor: Option[Double], cap: Option[Double], amt: Double) = {
(identity[Double] _ /: List(floor.map(f => (_: Double) max f), cap.map(c => (_: Double) min c)).flatten){ _ andThen _ }(amt)
}
Straightforward solution with plain Scala and anonymous lambda, without any mappings, folds, Double.{Min/Max}Value, and so on:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double =
((x:Double) => x min cap.getOrElse(x))(amt max floor.getOrElse(amt))
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)
This is another way to fix Landei's first answer
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
val chopBottom = (floor.getOrElse(amt) max amt)
chopBottom min cap.getOrElse(chopBottom)
}