Funktionales Äquivalent von if (p (f (a), f (b)) a else b
-
21-09-2019 - |
Frage
Ich vermute, dass es eine bessere funktionale Möglichkeit geben muss, Folgendes auszudrücken:
def foo(i: Any) : Int
if (foo(a) < foo(b)) a else b
Also in diesem Beispiel f == foo
und p == _ < _
. Dafür wird es in Scalaz eine meisterhafte Klugheit geben! Ich kann das sehen BooleanW
Ich kann schreiben:
p(f(a), f(b)).option(a).getOrElse(b)
Aber ich war mir sicher, dass ich einen Code schreiben könnte, der nur auf a und b einmal. Wenn dies vorhanden ist, muss es eine Kombination von sein Function1W
Und noch etwas anderes als Scalaz ist mir ein kleines Rätsel!
BEARBEITEN: Ich denke, ich frage hier nicht: "Wie schreibe ich das?" Aber "Was ist der richtige Name und die richtige Signatur für eine solche Funktion und hat es etwas mit FP -Sachen zu tun, das ich noch nicht wie Keisli, Comonad usw. verstehe?"
Lösung
Nur für den Fall, dass es nicht in Scalaz ist:
def x[T,R](f : T => R)(p : (R,R) => Boolean)(x : T*) =
x reduceLeft ((l, r) => if(p(f(l),f(r))) r else l)
scala> x(Math.pow(_ : Int,2))(_ < _)(-2, 0, 1)
res0: Int = -2
Alternative mit etwas Overhead, aber schöner Syntax.
class MappedExpression[T,R](i : (T,T), m : (R,R)) {
def select(p : (R,R) => Boolean ) = if(p(m._1, m._2)) i._1 else i._2
}
class Expression[T](i : (T,T)){
def map[R](f: T => R) = new MappedExpression(i, (f(i._1), f(i._2)))
}
implicit def tupleTo[T](i : (T,T)) = new Expression(i)
scala> ("a", "bc") map (_.length) select (_ < _)
res0: java.lang.String = a
Andere Tipps
Ich glaube nicht, dass Pfeile oder andere spezielle Berechnungsart hier nützlich sein können. Schließlich berechnen Sie mit normalen Werten und können normalerweise a anheben rein Berechnung in den speziellen Berechnungstypen (verwendet arr
für Pfeile oder return
für Monaden).
Ein sehr einfacher Pfeil ist jedoch arr a b
ist einfach eine Funktion a -> b
. Sie können dann Pfeile verwenden, um Ihren Code in primitivere Operationen aufzuteilen. Es gibt jedoch wahrscheinlich keinen Grund dafür und macht Ihren Code nur komplizierter.
Sie könnten zum Beispiel den Anruf anheben foo
so dass es getrennt vom Vergleich geschehen wird. Hier ist eine simiple Definition von Pfeilen in F# - sie erklärt ***
und >>>
Pfeilkombinatoren und auch arr
Um reine Funktionen in Pfeile zu verwandeln:
type Arr<'a, 'b> = Arr of ('a -> 'b)
let arr f = Arr f
let ( *** ) (Arr fa) (Arr fb) = Arr (fun (a, b) -> (fa a, fb b))
let ( >>> ) (Arr fa) (Arr fb) = Arr (fa >> fb)
Jetzt können Sie Ihren Code so schreiben:
let calcFoo = arr <| fun a -> (a, foo a)
let compareVals = arr <| fun ((a, fa), (b, fb)) -> if fa < fb then a else b
(calcFoo *** calcFoo) >>> compareVals
Das ***
Kombinator nimmt zwei Eingänge auf und führt die erste und zweite Funktion im ersten zweiten Argument aus. >>>
Dann komponiert diesen Pfeil mit dem Vergleich.
Aber wie gesagt - es gibt wahrscheinlich überhaupt keinen Grund dafür, dies zu schreiben.
Hier ist die mit Scalaz implementierte auf Arrow -basierte Lösung. Dies erfordert Kofferraum.
Sie erhalten keinen großen Sieg durch die Verwendung der Pfeilabstraktion mit einfachen alten Funktionen, aber es ist eine gute Möglichkeit, sie zu lernen, bevor Sie nach Kaisisli oder Coksiisli Arrows ziehen.
import scalaz._
import Scalaz._
def mod(n: Int)(x: Int) = x % n
def mod10 = mod(10) _
def first[A, B](pair: (A, B)): A = pair._1
def selectBy[A](p: (A, A))(f: (A, A) => Boolean): A = if (f.tupled(p)) p._1 else p._2
def selectByFirst[A, B](f: (A, A) => Boolean)(p: ((A, B), (A, B))): (A, B) =
selectBy(p)(f comap first) // comap adapts the input to f with function first.
val pair = (7, 16)
// Using the Function1 arrow to apply two functions to a single value, resulting in a Tuple2
((mod10 &&& identity) apply 16) assert_≟ (6, 16)
// Using the Function1 arrow to perform mod10 and identity respectively on the first and second element of a `Tuple2`.
val pairs = ((mod10 &&& identity) product) apply pair
pairs assert_≟ ((7, 7), (6, 16))
// Select the tuple with the smaller value in the first element.
selectByFirst[Int, Int](_ < _)(pairs)._2 assert_≟ 16
// Using the Function1 Arrow Category to compose the calculation of mod10 with the
// selection of desired element.
val calc = ((mod10 &&& identity) product) ⋙ selectByFirst[Int, Int](_ < _)
calc(pair)._2 assert_≟ 16
Nun, ich habe aufgesehen Hoogle für eine Typsignatur wie die in Thomas Jungs Antworten, und da ist on
. Darauf habe ich gesucht:
(a -> b) -> (b -> b -> Bool) -> a -> a -> a
Wo (a -> b)
ist das Äquivalent von foo
, (b -> b -> Bool)
ist das Äquivalent von <
. Leider die Unterschrift für on
Gibt etwas anderes zurück:
(b -> b -> c) -> (a -> b) -> a -> a -> c
Dies ist fast gleich, wenn Sie ersetzen c
mit Bool
und a
An den beiden Stellen erscheint es jeweils.
Im Moment vermute ich, dass es nicht existiert. Mir fiel, dass es eine allgemeinere Signatur gibt, also habe ich es auch versucht:
(a -> b) -> ([b] -> b) -> [a] -> a
Dieser ergab nichts.
BEARBEITEN:
Jetzt glaube ich nicht, dass ich überhaupt so weit war. Betrachten Sie zum Beispiel Folgendes:
Data.List.maximumBy (on compare length) ["abcd", "ab", "abc"]
Die Funktion maximumBy
Signatur ist (a -> a -> Ordering) -> [a] -> a
, was, kombiniert mit on
, ist ziemlich nahe an dem, was Sie ursprünglich angegeben haben, angesichts dessen Ordering
ist drei Werte - fast einen Booleschen! :-)
Also, sagen Sie, Sie haben geschrieben on
in Scala:
def on[A, B, C](f: ((B, B) => C), g: A => B): (A, A) => C = (a: A, b: A) => f(g(a), g(b))
Das du könntest schreiben select
so was:
def select[A](p: (A, A) => Boolean)(a: A, b: A) = if (p(a, b)) a else b
Und benutze es so:
select(on((_: Int) < (_: Int), (_: String).length))("a", "ab")
Was wirklich besser mit Currying und dot-freier Notation funktioniert. :-) Aber versuchen wir es mit impliziten:
implicit def toFor[A, B](g: A => B) = new {
def For[C](f: (B, B) => C) = (a1: A, a2: A) => f(g(a1), g(a2))
}
implicit def toSelect[A](t: (A, A)) = new {
def select(p: (A, A) => Boolean) = t match {
case (a, b) => if (p(a, b)) a else b
}
}
Dann kannst du schreiben
("a", "ab") select (((_: String).length) For (_ < _))
Sehr nah. Ich habe keine Möglichkeit gedacht, den Typ -Qualifikationsmerkmal von dort aus zu entfernen, obwohl ich vermute, dass dies möglich ist. Ich meine, ohne den Weg von Thomas zu beantworten. Aber vielleicht das ist der Weg. In der Tat denke ich on (_.length) select (_ < _)
liest besser als map (_.length) select (_ < _)
.
Dieser Ausdruck kann sehr elegant geschrieben werden Faktor -Programmiersprache - Eine Sprache, in der Funktionskomposition ist das Die Art und Weise, wie Dinge und der größte Code auf punktfreie Weise geschrieben sind. Der Stack Semantics and Row Polymorphism erleichtert diese Programmstil. So sieht die Lösung für Ihr Problem in Faktor aus:
# We find the longer of two lists here. The expression returns { 4 5 6 7 8 }
{ 1 2 3 } { 4 5 6 7 8 } [ [ length ] bi@ > ] 2keep ?
# We find the shroter of two lists here. The expression returns { 1 2 3 }.
{ 1 2 3 } { 4 5 6 7 8 } [ [ length ] bi@ < ] 2keep ?
Von unserem Interesse hier ist der Kombinator 2keep
. Es handelt sich um einen "Aufrechterhalten von Datenflusskombinator", was bedeutet, dass es seine Eingaben nach der Ausführung der angegebenen Funktion aufbewahrt.
Versuchen wir, diese Lösung (eine Art) Lösung in Scala zu übersetzen.
Zunächst definieren wir einen Arity-2-Erhaltskombinator.
scala> def keep2[A, B, C](f: (A, B) => C)(a: A, b: B) = (f(a, b), a, b)
keep2: [A, B, C](f: (A, B) => C)(a: A, b: B)(C, A, B)
Und ein eagerIf
Kombinator. if
Eine Kontrollstruktur kann in der Funktionszusammensetzung nicht verwendet werden. Daher dieses Konstrukt.
scala> def eagerIf[A](cond: Boolean, x: A, y: A) = if(cond) x else y
eagerIf: [A](cond: Boolean, x: A, y: A)A
Auch die on
Kombinator. Da es mit einer Methode mit demselben Namen von Scalaz zusammenspricht, werde ich es nennen upon
stattdessen.
scala> class RichFunction2[A, B, C](f: (A, B) => C) {
| def upon[D](g: D => A)(implicit eq: A =:= B) = (x: D, y: D) => f(g(x), g(y))
| }
defined class RichFunction2
scala> implicit def enrichFunction2[A, B, C](f: (A, B) => C) = new RichFunction2(f)
enrichFunction2: [A, B, C](f: (A, B) => C)RichFunction2[A,B,C]
Und jetzt setzen Sie diese Maschinerie ein!
scala> def length: List[Int] => Int = _.length
length: List[Int] => Int
scala> def smaller: (Int, Int) => Boolean = _ < _
smaller: (Int, Int) => Boolean
scala> keep2(smaller upon length)(List(1, 2), List(3, 4, 5)) |> Function.tupled(eagerIf)
res139: List[Int] = List(1, 2)
scala> def greater: (Int, Int) => Boolean = _ > _
greater: (Int, Int) => Boolean
scala> keep2(greater upon length)(List(1, 2), List(3, 4, 5)) |> Function.tupled(eagerIf)
res140: List[Int] = List(3, 4, 5)
Dieser Ansatz sieht in Scala nicht besonders elegant aus, aber zumindest zeigt er eine weitere Möglichkeit, Dinge zu tun.
Es gibt eine schöne Art, dies mit on
und Monad
, Aber Scala ist leider sehr schlecht in der punktfreien Programmierung. Ihre Frage lautet im Grunde genommen: "Kann ich die Anzahl der Punkte in diesem Programm reduzieren?"
Stellen Sie sich vor, wenn on
und if
wurden unterschiedlich Currys und gebürzt:
def on2[A,B,C](f: A => B)(g: (B, B) => C): ((A, A)) => C = {
case (a, b) => f.on(g, a, b)
}
def if2[A](b: Boolean): ((A, A)) => A = {
case (p, q) => if (b) p else q
}
Dann könnten Sie den Lesermonad verwenden:
on2(f)(_ < _) >>= if2
Das Haskell -Äquivalent wäre:
on' (<) f >>= if'
where on' f g = uncurry $ on f g
if' x (y,z) = if x then y else z
Oder...
flip =<< flip =<< (if' .) . on (<) f
where if' x y z = if x then y else z