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)
  }
}
10
demandé sur oxbow_lakes 2010-11-10 16:55:31

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...?

16
répondu Debilski 2010-11-12 13:39:24

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)
15
répondu Miles Sabin 2010-11-10 15:38:29

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.

5
répondu Daniel C. Sobral 2010-11-10 15:37:15

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
5
répondu retronym 2010-11-12 06:36:43

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)
4
répondu Landei 2010-11-10 15:45:19

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

4
répondu Kevin Wright 2010-11-12 12:19:25

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.

2
répondu Rex Kerr 2010-11-10 20:15:49

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)

( un Autre exemple du même principe. )

2
répondu Ken Bloom 2017-05-23 12:23:23

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 !

2
répondu oxbow_lakes 2017-05-23 11:45:12

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)
}
1
répondu Ken Bloom 2017-05-23 12:31:08

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)
}
0
répondu oxbow_lakes 2010-11-15 08:34:50

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))
0
répondu Nikolay Artamonov 2011-04-17 21:12:40

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) 
}
0
répondu user unknown 2011-08-06 03:11:21