2012-09-27 27 views
5

Tengo tres funciones (getRow, getColumn, getBlock) con dos argumentos (xey) que producen una lista del mismo tipo. Quiero escribir una cuarta función que concatena sus salidas:¿Cómo mapear una lista de funciones sobre múltiples argumentos en Haskell?

outputList :: Int -> Int -> [Maybe Int] 
outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

La función es eficaz, pero hay una manera de volver a escribir el doble mapa (con tres '$' s) a un solo mapa?

Respuesta

22
import Data.Monoid 

outputList :: Int -> Int -> [Maybe Int] 
outputList = mconcat [getRow, getColumn, getBlock] 

Se merecen una explicación.

Primero, señalaré explícitamente que todas estas funciones tienen el mismo tipo.

outputList, getRow, getColumn, getBlock :: Int -> Int -> [Maybe Int] 

Ahora vamos a empezar con su definición original.

outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

Estas funciones resultan en una [Maybe Int], y una lista de todo es un monoide. La combinación de listas monoidales es lo mismo que concatenar las listas, por lo que podemos reemplazar concat con mconcat.

outputList x y = mconcat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

Otra cosa que es un monoide es una función, si su resultado es un monoide. Es decir, si b es un monoide, entonces a -> b también es un monoide. Las funciones de combinación monoidales son lo mismo que llamar a las funciones con el mismo parámetro, y luego combinar los resultados de manera monoidal.

así podemos simplificar a

outputList x = mconcat $ map ($ x) [getRow,getColumn,getBlock] 

Y luego otra vez a

outputList = mconcat [getRow,getColumn,getBlock] 

Hemos terminado!


El Typeclassopedia has a section about monoids, aunque en este caso no estoy seguro de que añade que más allá de la documentation for Data.Monoid.

4

Como primer paso, se observa que su definición

outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

se puede reescribir usando el operador de composición de funciones (.) en lugar del operador aplicación de función ($) de la siguiente manera.

outputList x y = (concat . map ($ y) . map ($ x)) [getRow,getColumn,getBlock] 

A continuación nos damos cuenta de que map es otro nombre para fmap en las listas y que satisfaga los fmap leyes, por lo tanto, en particular, tenemos map (f . g) == map f . map g. Aplicamos esta ley para definir una versión con una sola aplicación de map.

outputList x y = (concat . map (($ y) . ($ x))) [getRow,getColumn,getBlock] 

Como paso final se puede reemplazar la composición de concat y map por concatMap.

outputList x y = concatMap (($ y) . ($ x)) [getRow,getColumn,getBlock] 

Por último, en mi opinión, aunque los programadores de Haskell tienden a utilizar muchos operadores de lujo, no es una vergüenza para definir la función de

outputList x y = concatMap (\f -> f x y) [getRow,getColumn,getBlock] 

como lo expresa claramente, lo que hace la función. Sin embargo, usar abstracciones de clase de tipo (como se demostró en la otra respuesta) puede ser una buena cosa ya que puede observar que su problema tiene una cierta estructura abstracta y obtener nuevos conocimientos.

2

Me gustaría ir con la respuesta de @ dave4420, ya que es muy conciso y expresa exactamente lo que quieres decir. Sin embargo, si usted no quiere depender de Data.Monoid entonces se podría reescribir de la siguiente manera

Código original:

outputList x y = concat . map ($ y) $ map ($ x) [getRow,getColumn,getBlock] 

Fusible los dos mapas:

outputList x y = concat . map (($y) . ($x)) [getRow,getColumn,getBlock] 

Reemplazar concat . map con concatMap:

outputList x y = concatMap (($y) . ($x)) [getRow,getColumn,getBlock] 

Y ya está.

Editar: aaaaand esto es exactamente lo mismo que @Jan Christiansen's answer. ¡Oh bien!

+0

Por cierto, ¿hay alguna estática sobre cuánto tiempo se tarda hasta que se responde una pregunta sobre Haskell? Tengo la impresión de que el 90% de todas las preguntas de Haskell se responden casi de inmediato. Si bien esto no dice nada sobre la calidad de las respuestas, en mi opinión también tienen una calidad bastante alta. –

+2

@JanChristiansen: Podría responder eso utilizando el explorador de datos de intercambio de pilas si lo desea. Hice una aproximación aproximada (filtre los valores atípicos obvios, ignoro las auto respuestas, etc.), y el tiempo típico (es decir, la mediana) fue de aproximadamente 20 minutos hasta que se publique la primera respuesta. –

Cuestiones relacionadas