Scala programmation fonctionnelle gymnastique
j'essaie de faire ce qui suit en aussi peu de code que possible et aussi fonctionnellement que possible:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double
évidemment les œuvres suivantes:
= (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
  }
j'espérais vraiment quelque chose de plus élégant et accepterai l'utilisation de la Scalaz bibliothèque! vous pouvez supposer que ce qui suit est vrai :
floor.forall( f => cap.forall( _ > f))
si quelqu'un est intéressé, voici un code d'essai :
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)
  }
}
13 réponses
Edit 2 :
 en pensant à la méthode cataX , j'ai compris que cataX  n'est rien d'autre qu'un simple pli. En utilisant cela, nous pouvons obtenir une solution de scala pure sans bibliothèques supplémentaires.  
donc, le voici:
( (amt /: floor)(_ max _) /: cap)(_ min _)
, qui est le même que
cap.foldLeft( floor.foldLeft(amt)(_ max _) )(_ min _)
(ce n'est pas nécessairement plus facile à comprendre).
je pense que vous ne pouvez pas l'avoir plus court que cela.
pour le meilleur et pour le pire, nous pouvons également le résoudre en utilisant scalaz:
floor.map(amt max).getOrElse(amt) |> (m => cap.map(m min).getOrElse(m))
ou même:
floor.cata(amt max, amt) |> (m => cap.cata(m min, m))
  en tant que programmeur Scala "normal", il est possible que l'on ne connaisse pas les opérateurs Scalaz spéciaux et les méthodes utilisées ( |>  et  Option.cata ). Ils travaillent comme suit:  
   value |> function   traduit par  function(value) et donc amt |> (m => v fun m) est égal à v fun amt .  
   opt.cata(fun, v)  se traduit par  
opt match {
  case Some(value) => fun(value) 
  case None => v
}
 ou opt.map(fun).getOrElse(v) .  
  voir les définitions Scalaz pour     cata     et     |>     .  
une solution plus symétrique serait:
amt |> (m => floor.cata(m max, m)) |> (m => cap.cata(m min, m))
      Edit:    Désolé, ça devient bizarre maintenant, mais je voulais avoir un point-version gratuite. La nouvelle cataX  est gravée. Le premier paramètre prend une fonction binaire; la seconde est une valeur.  
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)
  Si  o  correspond à  Some  nous de retour de la fonction avec la valeur de  o , et le second paramètre est appliqué, si  o  correspond à  None  nous retourner uniquement le deuxième paramètre.  
Et nous y voilà:
amt |> floor.cataX(_ max _) |> cap.cataX(_ min _)
peut-être Qu'ils ont déjà ça à Scalaz...?
pas tout à fait aussi courte que la version scalaz, mais d'un autre côté, pas de dépendances,
List(floor.getOrElse(Double.NegativeInfinity), cap.getOrElse(Double.PositiveInfinity), amt).sorted.apply(1)
je vais commencer par ceci:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  val flooring = floor.map(f => (_: Double) max f).getOrElse(identity[Double] _)       
  val capping  = cap.map(f => (_: Double) min f).getOrElse(identity[Double] _)         
  (flooring andThen capping)(amt)                                                      
}                                                                                    
mais j'ai le sentiment que je manque une occasion ici, donc je ne suis peut-être pas fini.
 plutôt que d'aller par pure brièveté, cela montre à quel point la composition devient plus facile si vous transformez cap et floor  en fonctions.  
scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>
scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>
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 = <function1>
scala> val fcap = orIdentity(cap)(min)                                             
fcap: (Int) => Int = <function1>
scala> val capAndFloor = fcap ∘ ffloor                                             
capAndFloor: (Int) => Int = <function1>    
scala> (0 to 8).toSeq ∘ (capAndFloor)    
res0: Seq[Int] = Vector(1, 1, 2, 3, 4, 5, 5, 5, 5)
 de scalaz, j'utilise MA#∘ , le functor map, à la fois comme une façon d'utiliser Option.map et Function1.andThen ; et OptionW#| qui est un alias pour Option.getOrElse .  
mise à JOUR
C'est ce que je cherchais:
scala> import scalaz._; import Scalaz._
import scalaz._
import Scalaz._
scala> val min = (scala.math.min _).curried                                        
min: (Int) => (Int) => Int = <function1>
scala> val max = (scala.math.max _).curried                                        
max: (Int) => (Int) => Int = <function1>
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: 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@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] est une enveloppe autour de A => A , Il ya des conversions implicites dans les deux sens. Il existe une instance de  Monoid  définie pour  Endo[A]  ,  Monoid#plus  enchaîne les fonctions, et  Monoid#zero  renvoie la fonction d'identité. Si nous avons un  List de Endo[A] , nous pouvons additionner la liste et obtenir une seule valeur, qui peut être utilisée comme A => A .  
   MA#foldMap  cartes " de la donnée fonction sur un type de données Foldable , et réduit à une seule valeur avec un Monoid .   foldMapEndo  est une commodité en plus de cela. Cette abstraction vous permet de passer facilement de l'essai du couvercle et du plancher dans  Option  à n'importe quel type pliable, comme un  List  .   
val capAndFloor = Seq(foldMapEndo(List(1, 2, max), foldMapEndo(cap, min)).collapsee
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon@76f40c39
un autre remaniement pourrait conduire à:
val capAndFloor = Seq((cap, min), (floor, max)).foldMap { case (a, f) => foldMapEndo(a, f) }
capAndFloor: scalaz.Endo[Int] = scalaz.Endos$$anon@25b85c8e
     Qu'en est-il?     
//WRONG
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
   (floor.getOrElse(amt) max amt) min cap.getOrElse(amt)
[Modifier]
Deuxième essai:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = 
   floor.map(f => f max _).getOrElse(identity[Double] _)(
     cap.map(c => c min _).getOrElse(identity[Double] _)(amt))
semble un peu trop "lispy" à mon goût, mais passe les tests :-)
[2e édition]
la première version peut être "réparée", aussi:
def restrict(floor: Option[Double], cap: Option[Double], amt: Double): Double =
  (floor.getOrElse(-Double.MaxValue) max amt) min cap.getOrElse(Double.MaxValue)
Pas plus beau, pas beaucoup plus courte, et certainement pas plus rapide! Mais il est plus composable plus générique et plus "fonctionnel":
MODIFIER : fait le code entièrement générique :)
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)
   mise à jour 2   : il y a aussi cette version, profitant de  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)
j'espère que vous aimez le type de signatures :)
UPDATE 3 : voici un qui fait la même chose avec limites
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)
}
si rien d'autre... cela montre très clairement pourquoi les paramètres d'importation seraient une bonne chose (tm). Imaginez si le code suivant était valide:
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)
}
JOUR 4 : mise en solution à l'envers ici. Celui-ci offre des possibilités plus intéressantes pour l'extension future.
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 
Avec un peu de chance, je vais inspirer quelqu'un d'autre à construire une meilleure solution à partir de la mienne. C'est la façon dont ces choses fonctionnent habituellement...
ce n'est pas vraiment plus facile à Scalaz qu'à 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
 (ajouter _ après max et min  si cela vous fait vous sentir mieux de voir où va le paramètre.)  
Scalaz est un peu plus facile à lire, cependant, une fois que vous comprenez ce que les opérateurs font.
 je trouve que lorsqu'une question demande d'utiliser un Option  pour indiquer un paramètre optionnel, il y a habituellement une façon plus naturelle de représenter le paramètre manquant. Je vais donc changer un peu l'interface ici, et utiliser des arguments par défaut pour définir la fonction et les paramètres nommés pour appeler la fonction.  
def restrict(amt:Double,
            floor:Double = Double.NegativeInfinity,
            cap:Double=Double.PositiveInfinity):Double =
    (amt min cap) max floor
alors vous pouvez appeler:
restrict(6)
restrict(6, floor = 7)
restrict(6, cap = 5)
C'est basé sur la réponse de Ken Bloom :
sealed trait Constrainer { def constrain(d : Double) : Double }
trait Cap extends Constrainer
trait Floor extends Constrainer
case object NoCap extends Cap { def constrain(d : Double) = d }
case object NoFloor extends Floor { def constrain(d : Double) = d }
implicit def d2cap(d : Double) = new Cap { def constrain(amt : Double) = d min amt }
implicit def d2floor(d : Double) = new Floor { def constrain(amt : Double) = d max amt }
def restrict(amt : Double, cap : Cap = NoCap, floor: Floor = NoFloor) : Double = {
  cap.constrain(floor.constrain(amt))
  //or (cap.constrain andThen floor.constrain) amt
}
il se termine avec le code d'écriture comme ceci:
restrict(amt, cap = 5D)
restrict(amt, floor = 0D)
je pense que c'est assez impressionnant et ne souffre pas du problème avec la solution de Ken (à mon avis), qui est qu'il est un hack !
C'est une autre façon de corriger la première réponse de Landei
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  val chopBottom = (floor.getOrElse(amt) max amt) 
  chopBottom min cap.getOrElse(chopBottom)
}
  j'ajoute une autre réponse qui a été inspirée à la fois par    retronym    et    Debilski    - fondamentalement, cela revient à transformer le couvercle et le plancher en fonctions ( Double => Double , s'ils sont présents) et ensuite plier la fonction d'identité à travers eux avec la 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)
}
solution simple avec Scala simple et lambda anonyme, sans aucune garniture, plis, Double.Valeur {Min / Max}, et ainsi de suite:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double =
  ((x:Double) => x min cap.getOrElse(x))(amt max floor.getOrElse(amt))
 j'aime la solution initiale avec le match-case le plus - outre le fait que je n'ai pas compris que amt signifie amount (en Allemagne," amt "signifie " bureau") et je savais seulement  cap  comme quelque chose que je porte sur ma tête ...  
voici maintenant une solution vraiment sans inspiration, en utilisant une méthode interne:
def restrict(floor : Option[Double], cap : Option[Double], amt : Double) : Double = {
  def restrict (floor: Double, cap: Double, amt: Double) =
    (floor max amt) min cap
  var f = floor.getOrElse (amt)            
  val c = cap.getOrElse (amt)   
  restrict (f, c, amt) 
}