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)
}