2010-02-19 14 views
14

supongo que debe haber una mejor manera funcional de expresar lo siguiente:equivalente funcional de si (p (f (a), f (b)) de una persona b

def foo(i: Any) : Int 

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

Así que en este . ejemplo f == foo y p == _ < _ hay muchas posibilidades de ser cierta destreza magistral en scalaz para este puedo ver que el uso de BooleanW puedo escribir:

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

pero estaba segura de que iba a ser capaz de escribir algo de código que solamente referido a a y b una vez. Si esto existe, debe estar en una combinación de Function1W y algo más, ¡pero Scalaz es un misterio para mí!

EDIT: Supongo que lo que estoy preguntando aquí no es "¿cómo escribo esto?" pero "¿Cuál es el nombre y la firma correctos para esa función y tiene algo que ver con cosas de FP que aún no entiendo como Kleisli, Comonad, etc.?"

+1

Tengo la vaga sensación de que esto podría expresarse muy bien con Arrows in Scalaz. Informará de nuevo con algo más concreto después :) – retronym

+1

'List (a, b) minBy foo'? – ron

Respuesta

6

Sólo 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 sobrecarga, pero la sintaxis más agradable.

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 
+0

Sí, me doy cuenta de que podría escribir la función en sí. Supongo que quería saber cuál era el nombre "correcto" para este tipo de cosas en FP. ¿Esto es con Kleisli whatsits? ¡No tengo idea! –

+1

Ya lo he asumido, pero no pude resistirme a escribirlo de todos modos. Kleisli whatsits es solo ruido blanco para mí. –

+0

¡Dale a 'x' un nombre descriptivo e incluso podría aceptar tu respuesta! –

3

Bueno, miré hacia arriba Hoogle para una firma de tipo como el de Thomas Jung'sanswer, y hay on. Esto es lo que han buscado:

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

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

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

Esto es casi lo mismo, si se reemplaza con cBool y a en los dos lugares que aparece, respectivamente.

Entonces, en este momento, sospecho que no existe. Se me ha ocurrido que hay una firma de tipo más general, por lo que lo probamos así:

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

Ésta dio nada.

EDIT:

Ahora no creo que yo estaba lejos de todo. Consideremos, por ejemplo, esto:

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

La función maximumBy firma es (a -> a -> Ordering) -> [a] -> a, lo que, combinado con on, es bastante cercano a lo especificado originalmente, dado que es Ordering tiene tres valores - casi un booleano!:-)

Por lo tanto, dicen que escribió 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 podría escribir select así:

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

Y utilizar de esta manera:

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

Lo que realmente funciona mejor con currying y notación sin puntos. :-) Pero vamos a intentarlo con implícitos:

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 usted puede escribir

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

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

+0

Hubo una pregunta sobre la función 'on' y Scala ya. Pero no pude encontrarlo de nuevo. No es exactamente una palabra fácil de buscar. –

+0

No pude encontrar la manera de redactar 'on' para que tuviera" acceso "a los datos originales. El problema es que esta pregunta es un "caso especial" para un predicado mientras que 'on' es más general –

+1

@oxbow Tanto el' on' como el 'For' - que no es más que un' on' invertido, para tomar ventaja de la inferencia de tipo: son particularmente estrictos sobre los tipos de datos. Solo 'select' está vinculado a' Pair' y 'Boolean'. ¿Cuál sería el caso más general? –

5

No creo que las flechas u otro tipo especial de cálculo puedan ser útiles aquí. Después de todo, está calculando con valores normales y generalmente puede levantar un cálculo puro que 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 puede usar flechas para dividir su código en operaciones más primitivas. Sin embargo, probablemente no haya ninguna razón para hacerlo y solo hace que su código sea más complicado.

Por ejemplo, puede levantar la llamada al foo para que se haga por separado de la comparación. He aquí una definición simiple de flechas en F # - declara *** y >>> combinadores de flecha y también arr para convertir funciones puras en las 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 se puede escribir el código como el siguiente:

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 

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

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

+7

No entiendo casi nada de esta respuesta. +1 –

4

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

No obtiene una gran ganancia al utilizar la abstracción de flecha con funciones simples, pero es una buena forma de aprenderlas antes de pasar a las flechas Kleisli o 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 
1

Esta expresión puede escribirse muy elegante en Factor programming language - un lenguaje donde la composición de funciones es la forma de hacer las cosas, y la mayoría del código está escrito en forma gratuita puntos. La semántica de la pila y el polimorfismo de fila facilitan este estilo de programación. Esto es lo que la solución a su problema se verá como 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 ] [email protected] > ] 2keep ? 

# We find the shroter of two lists here. The expression returns { 1 2 3 }. 
{ 1 2 3 } { 4 5 6 7 8 } [ [ length ] [email protected] < ] 2keep ? 

de nuestro interés aquí es el combinador 2keep. Es un "combinador de flujo de datos conservador", lo que significa que conserva sus entradas después de que la función dada se realiza en ellas.


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

Antes que nada, definimos un combinador conservador 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 siendo una estructura de control no se puede usar en la composición de funciones; 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 

Además, el on combinador. Como choca con un método con el mismo nombre de Scalaz, lo llamaré upon.

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 pon esta maquinaria a utilizar!

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 parece especialmente elegante en Scala, pero al menos se muestra una forma más de hacer las cosas.

+0

Por lo que vale, [una traducción de Haskell] (https://gist.github.com/1781624). – missingfaktor

1

Hay una buena forma de hacerlo con on y Monad, pero Scala lamentablemente es muy malo en la programación sin puntos. Su pregunta es básicamente: "¿puedo reducir el número de puntos en este programa?"

Imagínese si on y if eran 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 
} 

A continuación, se puede utilizar el lector de mónada:

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

El equivalente 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 
+0

¿Puede proporcionar también un equivalente de Haskell? – missingfaktor

+1

Aw hombre, que se ve mal. Gracias por cumplir con la solicitud sin embargo. +1! – missingfaktor

Cuestiones relacionadas