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
Rather than going for pure brevity, this shows how much easier composition becomes if you turn cap
and floor
into functions.
scala> val min = (scala.math.min _).curried
min: (Int) => (Int) => Int =
scala> val max = (scala.math.max _).curried
max: (Int) => (Int) => Int =
scala> def orIdentity[A](a: Option[A])(f: A => A => A): (A => A) = a ∘ f | identity
orIdentity: [A](a: Option[A])(f: (A) => (A) => A)(A) => A
scala> val cap = 5.some; val floor = 1.some
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)
scala> val ffloor = orIdentity(floor)(max)
ffloor: (Int) => Int =
scala> val fcap = orIdentity(cap)(min)
fcap: (Int) => Int =
scala> val capAndFloor = fcap ∘ ffloor
capAndFloor: (Int) => Int =
scala> (0 to 8).toSeq ∘ (capAndFloor)
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5)
From scalaz, I use MA#∘
, the functor map, both as a way of using Option.map
and Function1.andThen
; and OptionW#|
which is an alias for Option.getOrElse
.
UPDATE
This is what I was looking for:
scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._
scala> val min = (scala.math.min _).curried
min: (Int) => (Int) => Int =
scala> val max = (scala.math.max _).curried
max: (Int) => (Int) => Int =
scala> def foldMapEndo[F[_]: Foldable, A](fa: F[A], f: A => A => A): Endo[A] =
| fa.foldMap[Endo[A]](a => f(a))
foldMapEndo: [F[_],A](fa: F[A],f: (A) => (A) => A)(implicit evidence$1: scalaz.Foldable[F])scalaz.Endo[A]
scala> val cap = 5.some; val floor = 1.some
cap: Option[Int] = Some(5)
floor: Option[Int] = Some(1)
scala> val capAndFloor = List(foldMapEndo(floor, max), foldMapEndo(cap, min)) ∑
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@4352d1fc
scala>(0 to 10).toSeq.map(capAndFloor)
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5)
scalaz.Endo[A]
is a wrapper around A => A
, there are implicit conversions in both directions. There is an instance of Monoid
defined for Endo[A]
, Monoid#plus
chains the functions, and Monoid#zero
returns the identity function. If we have a List
of Endo[A]
, we can sum the list and result in a single value, which can be used as A => A
.
MA#foldMap
maps the given function over a Foldable
data type, and reduces to a single value with a Monoid
. foldMapEndo
is a convenience on top of this. This abstraction allows you to easily change from proving the cap and floor in Option
s to any foldable type, such as a List
.
val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@76f40c39
Another refactoring might lead to:
val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) }
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon$1@25b85c8e