Question

Je suppose qu'il doit y avoir une meilleure façon fonctionnelle d'exprimer ce qui suit:

def foo(i: Any) : Int

if (foo(a) < foo(b)) a else b 

Ainsi, dans cet exemple f == foo et p == _ < _. Il y a forcément une certaine habileté magistrale dans scalaz pour cela! Je peux voir que l'utilisation BooleanW je peux écrire:

p(f(a), f(b)).option(a).getOrElse(b)

Mais je suis sûr que je serais capable d'écrire un code qui se référait uniquement à et b une fois . Si cela existe, il doit être sur une combinaison de Function1W et quelque chose d'autre, mais scalaz est un peu un mystère pour moi!

EDIT : « Comment puis-je écrire ce » Je suppose que ce que je demande ici n'est pas mais « Quel est le nom correct et la signature pour une telle fonction et at-il quelque chose à voir avec des choses FP Je ne comprends pas encore comme Kleisli, Comonad etc? »

Était-ce utile?

La solution

Juste au cas où ce n'est pas en Scalaz:

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 avec certains frais généraux, mais plus jolie syntaxe.

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

Autres conseils

Je ne pense pas que les flèches ou tout autre type spécial de calcul peut être utile ici. Après tout, vous calculez des valeurs normales et vous pouvez généralement soulever un pur calcul qui dans le type de calcul spécial (à l'aide des flèches ou arr return pour monades).

Cependant, une flèche très simple est arr a b est simplement une a -> b fonction. Vous pouvez ensuite utiliser les flèches pour diviser votre code dans des opérations plus primitives. Cependant, il n'y a probablement pas de raison de le faire et il ne fait que votre code plus complexe.

Vous pouvez par exemple soulever l'appel à foo afin qu'il se fait séparément de la comparaison. Voici une définition de simiple de flèches en F # - il déclare *** et >>> flèche combinateurs et aussi arr pour transformer les fonctions pures en flèches:

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)

Maintenant, vous pouvez écrire votre code comme ceci:

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

Le *** combinateur prend deux entrées et dirige la première et la seconde fonction spécifiée sur la première, respectivement deuxième argument. >>> compose alors cette flèche avec celui qui fait la comparaison.

Mais comme je l'ai dit - il n'y a probablement aucune raison pour écrire cela.

Voici la solution Arrow, mis en œuvre avec Scalaz. Cela nécessite le tronc.

Vous ne recevez pas une grande victoire d'utiliser l'abstraction flèche avec des fonctions pures et simples, mais il est un bon moyen de les apprendre avant de passer à des flèches Kleisli ou Cokleisli.

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

Eh bien, je levais les yeux Hoogle pour une signature de type comme celui de Thomas Jung réponse , et il y a on . Voilà ce que je recherchais:

(a -> b) -> (b -> b -> Bool) -> a -> a -> a

(a -> b) est l'équivalent de foo, (b -> b -> Bool) est l'équivalent de <. Malheureusement, la signature pour on retourne quelque chose d'autre:

(b -> b -> c) -> (a -> b) -> a -> a -> c

Ceci est presque le même, si vous remplacez c avec Bool et a dans les deux endroits, il apparaît respectivement.

Alors, en ce moment, je pense qu'il n'existe pas. Il me est survenue à ce qu'il ya une signature de type plus général, donc je l'ai essayé ainsi:

(a -> b) -> ([b] -> b) -> [a] -> a

Celui-ci n'a rien donné.

EDIT:

Maintenant, je ne pense pas que j'étais loin du tout. Prenons, par exemple, ceci:

Data.List.maximumBy (on compare length) ["abcd", "ab", "abc"]

La signature fonction maximumBy est (a -> a -> Ordering) -> [a] -> a, qui, avec on, est assez proche de ce que vous avez initialement spécifié, étant donné que Ordering est a trois valeurs - presque un booléen! : -)

Alors, dites que vous avez écrit dans on 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))

vous pouvez écrire select comme ceci:

def select[A](p: (A, A) => Boolean)(a: A, b: A) = if (p(a, b)) a else b

Et l'utiliser comme ceci:

select(on((_: Int) < (_: Int), (_: String).length))("a", "ab")

Ce qui fonctionne vraiment mieux avec curryfication et notation sans point. :-) Mais nous allons essayer avec implicits:

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

Ensuite, vous pouvez écrire

("a", "ab") select (((_: String).length) For (_ < _))

Très proche. Je n'ai pas trouvé un moyen de supprimer le qualificatif de type à partir de là, mais je pense qu'il est possible. Je veux dire, sans aller le chemin de la réponse Thomas. Mais peut-être que le chemin. En fait, je pense que on (_.length) select (_ < _) lit mieux que map (_.length) select (_ < _).

Cette expression peut être écrit avec beaucoup d'élégance dans langage de programmation facteur - une langue où la composition de fonction est la façon de faire, et la plupart du code est écrit de manière ponctuelle sans. La sémantique de la pile et polymorphisme ligne facilite ce style de programmation. C'est ce que la solution à votre problème ressemblera en facteur:

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

De notre intérêt est ici le combinateur 2keep . Il est un « préserver le flux de données-combinateur », ce qui signifie qu'il conserve ses entrées après la fonction donnée est effectuée sur eux.


Essayons de traduire (sorte de) cette solution à Scala.

Tout d'abord, nous définissons une préservation arité-2 Combinator.

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)

Et un combinateur eagerIf. if étant une structure de commande ne peut pas être utilisé dans la composition de fonctions; par conséquent, cette construction.

scala> def eagerIf[A](cond: Boolean, x: A, y: A) = if(cond) x else y
eagerIf: [A](cond: Boolean, x: A, y: A)A

En outre, le on Combinator. Depuis elle se heurte à une méthode avec le même nom de Scalaz, je le nomme upon à la place.

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]

Et maintenant mettre cette machine à utiliser!

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)

Cette approche ne semble pas particulièrement élégante à Scala, mais au moins il vous montre une autre façon de faire les choses.

Il y a une belle façon à la rigueur de le faire avec on et Monad, mais Scala est malheureusement très mal à la programmation de point gratuit. Votre question est essentiellement: « puis-je réduire le nombre de points dans ce programme »

Imaginez si on et if étaient différemment et cari uplées:

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
}

Ensuite, vous pouvez utiliser le lecteur monade:

on2(f)(_ < _) >>= if2

L'équivalent Haskell serait:

on' (<) f >>= if'
  where on' f g = uncurry $ on f g
        if' x (y,z) = if x then y else z

Ou ...

flip =<< flip =<< (if' .) . on (<) f
  where if' x y z = if x then y else z
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top