¿Cómo crear una función haskell polyvariadic?

Necesito una función que tome un número arbitrario de argumentos (todos del mismo tipo), haga algo con ellos y luego devuelva un resultado. Una lista de argumentos es impracticable en mi caso específico.

Mientras miraba a través de las librerías 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 por la que necesito esto es realmente bastante simple. Para la escuela (clase de informática), debemos escribir un módulo que sea capaz de “grabar” una expresión matemática, expressla 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 específica. Uno de los objectives es escribir una función, que toma esa expresión con un cierto número de variables (pares de tipo (Char,Rational) ) y calcula el resultado de la expresión. Deberíamos ver cómo express mejor el objective 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).

Los puntos clave de printf es la capacidad de devolver un String o una función. Copiado 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 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 usar

 *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 

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

En el artículo de la wiki sobre funciones variadas, se hizo referencia a este artículo . Supongo que esto es lo que printf hace, pero tampoco lo entiendo. De todos modos, esto es ciertamente un exceso, 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.

Eché un vistazo a un ejemplo vinculado desde el artículo al que hace referencia Delnan. Después de mirarlo por un momento, creo que finalmente comprendo lo que está pasando:

Comienza con esta clase de tipo:

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

Ese bit después de la tubería (|) es una dependencia funcional. Dice que el tipo representado por a puede ser determinado por el tipo representado por r . En otras palabras, no puede definir dos instancias de la BuildList BuildList con el mismo r (tipo de retorno), pero diferentes a .

Saltando un poco hacia donde realmente se usa la función build' :

 > build True :: [Bool] 

Como build solo llama a build' con una lista vacía como primer parámetro, esto es lo mismo que:

 > build' [] True :: [Bool] 

En este ejemplo, build' está regresando claramente una lista. Debido a la dependencia funcional, solo podemos enlazar a esta instancia de la clase de tipo BuildList :

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

Muy claro. El segundo ejemplo es más interesante. Ampliando 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 precedencia de Haskell significan que lo 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 esa expresión a un parámetro con el valor’ False ‘. En otras palabras, se espera que la expresión (build' [] True) devuelva una función de tipo Bool -> [Bool] . Y eso nos vincula a la segunda instancia de la BuildList de BuildList BuildList:

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

En esta invocación, l = [] y x = True e y = False , por lo que la definición se expande 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í.

La respuesta de KennyTM es genial. A continuación se muestra un ejemplo del proceso de sumOf 1 4 7 10 :: Integer 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 
    Intereting Posts