¿Cómo crear una función haskell polyvariadic?
variadic-functions function-parameter (5)
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, 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 específica. Uno de los objetivos 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 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).
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 a r | 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'' l x = 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 a r => BuildList a (a->r) where
build'' l x y = 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í.
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.
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
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)].