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

War es hilfreich?

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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top