58

Necesito una función que toma un número arbitrario de argumentos (Todos del mismo tipo), hace algo con ellos y luego devuelve un resultado. Una lista de argumentos es impracticable en mi caso específico.¿Cómo crear una función haskell polyvariadic?

Al examinar las librerías de haskell, vi que la función printf (del módulo Text.Printf) usa un truco similar. Desafortunadamente, no pude entender esa magia mirando la fuente.

¿Alguien puede explicar cómo lograr esto, o al menos alguna página web/papel/lo que sea, donde podría encontrar una buena descripción para esto?

motivación:

La razón que necesito esto es realmente muy simple. Para la escuela (clase de informática), debemos escribir un módulo que sea capaz de "grabar" una expresión matemática, expresarla como una cadena (mediante la escritura de una instancia de Num/Real/etc. para un tipo de datos propio) y realizar varias operaciones en él.

Este tipo de datos contiene un constructor especial para una variable, que puede ser reemplazado por un valor o lo que sea por una función especificada. Uno de los objetivos es escribir una función, que toma dicha expresión con un número de variables (pares del tipo (Char,Rational)) y calcula el resultado de la expresión. Deberíamos ver cómo expresar mejor el objetivo de la función. (Mi idea: la función devuelve otra función que toma exactamente tantos argumentos como vars que están definidos en la función - parece ser imposible).

+0

Sería interesante ver su caso de uso para esto. – ShiDoiSi

+0

Solo un proyecto para la escuela. Editaré la pregunta y publicaré el propósito. – fuz

+3

Puede considerar otros enfoques: una función que toma una lista: evalúa :: [(Char, Rational)] -> Expr -> Racional; una función que toma una función: (Char -> Racional) -> Expr -> Racional; una función que toma un mapa: Data.Map.Map Char Rational -> Expr -> Rational. – sdcvvc

Respuesta

85

Los puntos clave de printf es la capacidad de devolver un String o una función. Copiada de http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Text-Printf.html,

printf :: (PrintfType r) => String -> r 
printf fmts = spr fmts [] 

class PrintfType t where 
    spr :: String -> [UPrintf] -> t 

instance (IsChar c) => PrintfType [c] where 
    spr fmts args = map fromChar (uprintf fmts (reverse args)) 

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where 
    spr fmts args = \a -> spr fmts (toUPrintf a : args) 

y la estructura básica que podemos extraer a cabo es

variadicFunction :: VariadicReturnClass r => RequiredArgs -> r 
variadicFunction reqArgs = variadicImpl reqArgs mempty 

class VariadicReturnClass r where 
    variadicImpl :: RequiredArgs -> AccumulatingType -> r 

instance VariadicReturnClass ActualReturnType where 
    variadicImpl reqArgs acc = constructActualResult reqArgs acc 

instance (ArgClass a, VariadicReturnClass r) => VariadicReturnClass (a -> r) where 
    variadicImpl reqArgs acc = \a -> variadicImpl reqArgs (specialize a `mappend` acc) 

Por ejemplo:

class SumRes r where 
    sumOf :: Integer -> r 

instance SumRes Integer where 
    sumOf = id 

instance (Integral a, SumRes r) => SumRes (a -> r) where 
    sumOf x = sumOf . (x +) . toInteger 

entonces podríamos utilizar

*Main> sumOf 1 :: Integer 
1 
*Main> sumOf 1 4 7 10 :: Integer 
22 
*Main> sumOf 1 4 7 10 0 0 :: Integer 
22 
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer 
59 
+0

Gracias KennyTM! Eso fue exactamente, ¡lo que estaba buscando! – fuz

+0

+1 Brillante truco! – Dario

+0

@KennyTM: ¿Sabe si existe una etiqueta para funciones polivalentes? – fuz

7

En el artículo de la wiki sobre funciones variadic, se hizo referencia a this article. Supongo que esto es lo que printf hace, pero tampoco lo entiendo. De todos modos, esto es ciertamente una exageración, especialmente porque tus argumentos son todos del mismo tipo. Solo ponlos a todos en una lista. Para eso están listas las listas: una cantidad arbitraria de cosas del mismo tipo. Bien, no es muy bonito, pero difícilmente será más feo que una función polivariada completa.

+0

Aunque esto es un poco confuso, esto parece ser eso, lo que necesito. – fuz

6

Tomé una mirada a an example vinculado desde el artículo al que hace referencia el delnan. Después de mirar fijamente durante un rato, creo que finalmente comprender lo que está pasando:

Se inicia con esta clase de tipo:

class BuildList a r | r-> a where 
    build' :: [a] -> a -> r 

Que poco después de que la barra vertical (|) es una dependencia funcional. Dice que el tipo representado por a se puede determinar por el tipo representado por r. En otras palabras, no puede definir dos instancias de la clase de tipo BuildList con el mismo r (tipo de devolución), pero diferente a.

Saltando por delante un poco para que se utiliza realmente la función build':

> build True :: [Bool] 

Desde build se acaba llamando build' con una lista vacía como primer parámetro, esto es lo mismo que:

> build' [] True :: [Bool] 

En este ejemplo, build' claramente muestra una lista. Debido a la dependencia funcional, que sólo pueden ser vinculantes para esta instancia de la clase BuildList Tipo:

instance BuildList a [a] where 
    build' l x = reverse$ x:l 

bastante sencillo. El segundo ejemplo es más interesante. La ampliación de la definición de build, se convierte en:

> build' [] True False :: [Bool] 

¿Cuál es el tipo de build' en este caso? Bueno, las reglas de prioridad de Haskell significan que el anterior también podría escribirse así:

> (build' [] True) False :: [Bool] 

Ahora queda claro que estamos pasando dos parámetros para build' y luego aplicar el resultado de que la expresión de un parámetro con valor 'Falso'. En otras palabras, se espera que la expresión (build' [] True) devuelva una función del tipo Bool -> [Bool]. Y que nos une a la segunda instancia de la clase de tipos BuildList:

instance BuildList a r => BuildList a (a->r) where 
    build' l x y = build'(x:l) y 

En esta invocación, l = [] y x = True y y = False, por lo que la definición se amplía para build' [True] False :: [Bool]. Esa firma se une a la primera instancia de build', y es bastante obvio a dónde va desde allí.

+0

Esta descripción es extremadamente sencilla y muy útil. Lo único que no queda claro para mí es esa definición 'build 'l x = reverse $ x: l'. Pero en mi opinión, la respuesta de KennyTM es más útil, porque prefiero iterar sobre los parámetros que presentarlos como una lista. – fuz

10

Mucha gente te dice cómo crear funciones variadas, pero creo que en este caso, en realidad es mejor utilizar una lista de tipo [(Char, Rational)].

+2

Por favor, mira, delnan ha dicho exactamente esto. Sí, lo sé, esto sería más fácil, pero es muy útil comprender cómo funciona esta mecánica al implementarla en un programa propio. – fuz

+7

También algo a tener en cuenta: esta es la forma * idiomática * de escribirlo también. La única función variada que creo que he usado es 'printf'; Espero que tu función tome una lista. –

+0

El único otro uso sensato de una función variadic que puedo pensar (excepto 'printf') sería diseñar una DSL para la que exista tal requisito específico. –

2

La respuesta de KennyTM es genial. A continuación se muestra un ejemplo del proceso ejecutivo de sumOf 1 4 7 10 :: Integer para dar una mejor ilustración.

sumOf 1 4 7 10 
((\ x -> (sumOf . (x +) . toInteger) 1) 4 7 10 
((sumOf . (1 +) . toInteger) 4) 7 10 
(sumOf 5) 7 10 
(sumOf . (5 +) . toInteger) 7 10 
sumOf 12 10 
sumOf . (12 +) . toInteger 10 
sumof 22 
id 22 
22 
Cuestiones relacionadas