2011-06-09 14 views
11
import Control.Applicative 
import Control.Arrow 

filter ((&&) <$> (>2) <*> (<7)) [1..10] 
filter ((>2) &&& (<7) >>> uncurry (&&)) [1..10] 

¡Ambos obtienen el mismo resultado! Sin embargo, me es MUY difícil de entender. ¿Podría alguien aquí explicarlo en detalle?¿Te importaría explicar el código en el foro?

+0

¿De dónde vienen estos ejemplos? –

+0

Los solicitantes realmente necesitan su propia sintaxis de azúcar, tanto para la aplicación regular como infija. 'filter ((> 2) <(&&)> (<7)) [1..10]' leería muy bien. –

+0

@pelotom: Siempre hay corchetes idiomáticos. Ahora tenemos mónadas de comprensión, ¿por qué no comprensiones aplicativas? :) –

Respuesta

23

Comencemos con el segundo, que es más simple. Tenemos dos operadores misteriosos aquí, con los siguientes tipos:

(&&&) :: Arrow a => a b c -> a b c' -> a b (c, c') 
(>>>) :: Category cat => cat a b -> cat b c -> cat a c 

Los Arrow y Category tipo clases son en su mayoría de las cosas que se comportan como funciones, que por supuesto incluye funciones de sí mismos, y ambos casos aquí son simplemente (->). Por lo tanto, la reescritura de los tipos de usar que:

(&&&) :: (b -> c) -> (b -> c') -> (b -> (c, c')) 
(>>>) :: (a -> b) -> (b -> c) -> (a -> c) 

El segundo tiene un tipo muy similar a (.), el conocido operador de composición de funciones; de hecho, son lo mismo, solo con argumentos intercambiados. El primero es más desconocido, pero los tipos nuevamente te dicen todo lo que necesitas saber: toma dos funciones, ambas tomando un argumento de un tipo común, y produce una única función que da los resultados de ambos combinados en una tupla.

Por lo tanto, la expresión (>2) &&& (<7) toma un solo número y produce un par de valores Bool según las comparaciones. El resultado de esto se introduce en uncurry (&&), que solo necesita un par de Bool sy ANDs juntos. El predicado resultante se usa para filtrar la lista de la manera habitual.


El primero es más críptico. Tenemos dos operadores misteriosos, de nuevo, con los siguientes tipos:

(<$>) :: Functor f => (a -> b) -> f a -> f b 
(<*>) :: Applicative f => f (a -> b) -> f a -> f b 

Observe que el segundo argumento de (<$>) en este caso es (>2), que tiene el tipo (Ord a, Num a) => a -> Bool, mientras que el tipo de argumento (<$>) 's tiene el tipo f a. ¿Cómo son compatibles?

La respuesta es que, del mismo modo que podríamos sustituir (->) para a y cat en las firmas de tipos anteriores, podemos pensar en a -> Bool como (->) a Bool, y sustituir ((->) a) para la f. Por lo tanto, la reescritura de los tipos, utilizando ((->) t) lugar para evitar chocar con otro tipo de variable a:

(<$>) :: (a -> b) -> ((->) t) a -> ((->) t) b 
(<*>) :: ((->) t) (a -> b) -> ((->) t) a -> ((->) t) b 

Ahora, poner las cosas en forma infija normales:

(<$>) :: (a -> b) -> (t -> a) -> (t -> b) 
(<*>) :: (t -> (a -> b)) -> (t -> a) -> (t -> b) 

La primera resulta ser función composición, como se puede observar de los tipos. El segundo es más complicado, pero una vez más los tipos te dicen lo que necesitas: toma dos funciones con un argumento de un tipo común, uno que produce una función y el otro que produce un argumento para pasar a la función. En otras palabras, algo como \f g x -> f x (g x). (Esta función también se conoce como S combinator en lógica combinatoria, un tema explorado ampliamente por el lógico Haskell Curry, cuyo nombre sin duda parece extrañamente familiar.)

La combinación de (<$>) y (<*>) especie de "se extiende" lo (<$>) único que hace, que en este caso significa tomar una función con dos argumentos, dos funciones con un tipo de argumento común, la aplicación de un único valor a los dos últimos, luego aplicando la primera función a los dos resultados. Por lo tanto, ((&&) <$> (>2) <*> (<7)) x se simplifica a (&&) ((>2) x) ((<7) x), o con el estilo infix normal, x > 2 && x < 7. Como antes, la expresión compuesta se usa para filtrar la lista de la manera habitual.


Además, tenga en cuenta que, si bien ambas funciones se ofuscado hasta cierto punto, una vez que se acostumbre a los operadores utilizados, que son en realidad bastante legible. Los primeros resúmenes sobre una expresión compuesta haciendo varias cosas para un solo argumento, mientras que el segundo es una forma generalizada del estilo estándar de "interconexión" de enhebrar cosas junto con la composición de funciones.

Personalmente, realmente encuentro el primero perfectamente legible. ¡Pero no espero que la mayoría de la gente esté de acuerdo!

+0

gracias por su respuesta! ¡Me aclaro mucho! –

+0

+1: Explicaste mi código mucho mejor de lo que pude. – Landei

+3

Yo agregaría que los Haskellers no hacen el tipo de gimnasia que se demuestra aquí en sus cabezas todo el tiempo. En cambio, el patrón: 'f <$> x <*> y' (también escrito:' liftA2 f x y') es bien conocido. Significa: Aplicar la función 'f' en dos valores envueltos por Applicative. Las funciones se pueden ver como "valores envueltos": '(> 2)' y '(<7)' pueden verse como Bools envueltos, donde 'Num a => ((->) a)' es el "envoltorio". La función '(&&)' se puede aplicar así en dos bools "envueltos" para producir el resultado bool envuelto. Lo mismo se puede usar para definir: 'avg = liftA2 (/) sum length' – Peaker

Cuestiones relacionadas