Pregunta

Supongo que debe haber una mejor forma funcional de expresar lo siguiente:

def foo(i: Any) : Int

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

Entonces en este ejemplo f == foo y p == _ < _. ¡Es probable que haya una inteligencia magistral en Scalaz para esto! Puedo ver eso usando BooleanW Puedo escribir:

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

Pero estaba seguro de que podría escribir algún código que solo referido a a y b una vez. Si esto existe, debe estar en alguna combinación de Function1W ¡Y algo más que Scalaz es un misterio para mí!

EDITAR: Supongo que lo que pregunto aquí no es "¿Cómo escribo esto?" Pero "¿Cuál es el nombre y la firma correctos para tal función y tiene algo que ver con las cosas de FP que aún no entiendo como Kleisli, Comonad, etc.?"

¿Fue útil?

Solución

En caso de que no esté 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

Alternativa con algo de sintaxis superior pero mejor.

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

Otros consejos

No creo que las flechas o ningún otro tipo especial de cálculo puedan ser útiles aquí. Después de todo, estás calculando con valores normales y generalmente puedes levantar un puro cálculo eso en el tipo especial de cálculo (usando arr para flechas o return para mónadas).

Sin embargo, una flecha muy simple es arr a b es simplemente una función a -> b. Luego podría usar flechas para dividir su código en operaciones más primitivas. Sin embargo, probablemente no haya razón para hacer eso y solo hace que su código sea más complicado.

Podrías, por ejemplo, levantar la llamada a foo para que se realice por separado de la comparación. Aquí hay una definición Simiple de flechas en f#: declara *** y >>> Combinadores de flecha y también arr Para convertir las funciones puras en flechas:

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)

Ahora puede escribir su código así:

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

los *** Combinator toma dos entradas y ejecuta la primera y segunda función especificada en el primer argumento, respectivamente segundo. >>> Luego compone esta flecha con la que hace comparación.

Pero como dije, probablemente no haya ninguna razón para escribir esto.

Aquí está la solución basada en la flecha, implementada con Scalaz. Esto requiere tronco.

No obtienes una gran victoria al usar la abstracción de Arrow con funciones antiguas, pero es una buena manera de aprenderlos antes de mudarse a Kleisli o Cokleisli Arrows.

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

Bueno, miré hacia arriba Hoogle para una firma de tipo como la de Thomas Jung's responder, y ahí está on. Esto es lo que busqué:

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

Dónde (a -> b) es el equivalente de foo, (b -> b -> Bool) es el equivalente de <. Desafortunadamente, la firma para on Devuelve algo más:

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

Esto es casi lo mismo, si reemplaza c con Bool y a En los dos lugares aparece, respectivamente.

Entonces, en este momento, sospecho que no existe. Se me ocurrió que hay una firma de tipo más general, así que también la probé:

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

Este no arrojó nada.

EDITAR:

Ahora no creo que estuviera tan lejos. Considere, por ejemplo, esto:

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

La función maximumBy La firma es (a -> a -> Ordering) -> [a] -> a, que, combinado con on, está bastante cerca de lo que especificó originalmente, dado que Ordering IS tiene tres valores, ¡casi un booleano! :-)

Entonces, digamos que escribiste on En 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))

El que podrías escribir select como esto:

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

Y úsalo así:

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

Lo que realmente funciona mejor con el curry y la notación sin puntos. :-) Pero probemos con implicados:

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

Entonces puedes escribir

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

Muy cerca. No he imaginado ninguna forma de eliminar el calificador de tipo de allí, aunque sospecho que es posible. Quiero decir, sin seguir el camino de la respuesta de Thomas. Pero tal vez eso es la manera. De hecho, creo on (_.length) select (_ < _) lee mejor que map (_.length) select (_ < _).

Esta expresión se puede escribir muy elegantemente en Lenguaje de programación de factores - un lenguaje donde la composición de la función es la forma de hacer las cosas, y la mayoría del código se escribe de manera libre. La semántica de la pila y el polimorfismo de fila facilita este estilo de programación. Así es como se verá la solución a su problema en el factor:

# 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 nuestro interés aquí está el combinador 2keep. Es un "conservador de datos de datos", lo que significa que conserva sus entradas después de que se realiza la función dada.


Intentemos traducir (más o menos) esta solución a Scala.

En primer lugar, definimos un combinador de preservación de arity-2.

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)

Y un eagerIf combinador. if Ser una estructura de control no se puede utilizar en la composición de la función; De ahí esta construcción.

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

También el on combinador. Dado que choca con un método con el mismo nombre de Scalaz, lo nombraré upon en cambio.

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]

¡Y ahora ponga esta maquinaria para usar!

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)

Este enfoque no se ve particularmente elegante en Scala, pero al menos le muestra una forma más de hacer las cosas.

Hay una forma agradable de hacer esto con on y Monad, pero Scala es desafortunadamente muy mala en la programación sin puntuación. Su pregunta es básicamente: "¿Puedo reducir la cantidad de puntos en este programa?"

Imaginar si on y if fueron de manera diferente al curry y tupled:

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
}

Entonces podrías usar la monad del lector:

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

El equivalente de Haskell sería:

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

O...

flip =<< flip =<< (if' .) . on (<) f
  where if' x y z = if x then y else z
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top