2010-09-23 7 views
5

Sigue siendo un novato Haskell aquí. Sé lo suficiente como para meterme en problemas con suposiciones equivocadas. Si tengo la siguiente función ...Pasar los elementos de la lista como parámetros a la función al curry

quadsum w x y z = w+x+y+z 

quiero una función que puede tener una lista, utilice cada elemento como un parámetro en una función especificada como quadsum, y devolver una función de curry para su uso posterior.

He estado tratando algo en el sentido de ...

magicalFunctionMaker f [] = (f) 
magicalFunctionMaker f (x:xs) = magicalFunctionMaker (f x) xs 

Con la esperanza de ser capaz de hacer esto ...

magicalFunctionMaker (quadsum) [4,3,2] 

Conseguir una función al curry como .. .:

(((quadsum 4) 3) 2) 

O, como alternativa, llame al:

magicalFunctionMaker (quadsum) [4,3,2,1] 

Resultando en ...

((((quadsum 4) 3) 2) 1) 

Es esto posible? ¿Qué tan equivocado soy?

+0

Eche un vistazo rápido a las funciones polyvariadic, pero puede hacer que su mente: [http://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function] – fuz

Respuesta

3

La respuesta de Paul Johnson prácticamente lo cubre. Sólo hacer

quadsum 4 3 2 

y el resultado será la función que desea, con el tipo Integer -> Integer.

Pero a veces esto no es suficiente. A veces obtienes listas de números, no sabes cuánto duran las listas, y necesitas aplicar los elementos a tu función. Esto es un poco mas dificil No puede hacer:

magicalFunction2 f [] = f 
magicalFunction2 f (x1:x2:xs) = f x1 x2 

porque los resultados tienen diferentes tipos. En el primer caso, el resultado necesita dos argumentos, y en el segundo, es una función completamente aplicada, por lo que no se permiten más argumentos. En este caso, lo mejor que puede hacer es aferrarse a la lista y su función original hasta que hay suficientes argumentos están disponibles:

type PAPFunc f a result = Either (f, [a]) result 

magicfunc f xs = Left (f,xs) 

apply (Left (f,xs)) ys = Left (f,xs++ys) 
apply p _    = p 

simp2 :: PAPFunc (a->a->b) a b -> PAPFunc (a->a->b) a b 
simp2 (Left (f,(x1:x2:xs))) = Right (f x1 x2) 
simp2 p = p 

ahora se puede hacer:

Main> let j = magicfunc (+) [] 
Main> let m = apply j [1] 
Main> let n = apply m [2,3] 

Main> either (const "unfinished") show $ simp2 m 
"unfinished" 
Main> either (const "unfinished") show $ simp2 n 
"3" 

usted necesitará un Simplificar separada función para cada arity, un problema que es fácilmente solucionado por Template Haskell.

El uso de listas de argumentos (a diferencia de un argumento de listas) tiende a ser muy incómodo en Haskell porque todos los resultados múltiples tienen diferentes tipos, y hay muy poco apoyo para colecciones con números variables de argumentos de tipos diferentes. He visto tres categorías generales de soluciones:

  1. Código explícitamente para cada caso por separado (rápidamente se convierte en una gran cantidad de trabajo ).

  2. Plantilla Haskell.

  3. Hackeo de sistema de tipo.

Mi respuesta se trata principalmente de tratar de hacer 1 menos doloroso. 2 y 3 no son para los débiles de corazón.

Editar: Resulta que hay somepackages en Hackage que están relacionados con este problema. Usando "iteratee":

import qualified Data.Iteratee as It 
import Control.Applicative 

magic4 f = f <$> It.head <*> It.head <*> It.head <*> It.head 

liftedQuadsum = magic4 quadsum 
-- liftedQuadsum is an iteratee, which is essentially an accumulating function 
-- for a list of data 

Main> p <- It.enumChunk (It.Chunk [1]) liftedQuadsum 
Main> It.run p 
*** Exception: EofException 
Main> q <- It.enumChunk (It.Chunk [2,3,4]) p 
Main> It.run q 
10 

Pero "iteratee" y "enumerator" son probablemente exagerados.

+0

Gracias. Esto ayuda a aclarar mucho. – Ishpeck

+0

'En el primer caso, el resultado necesita dos argumentos' - si esto no fuera' necesita un argumento' - '4 3 2' necesita' 1' –

1

No voy a trabajar, creo. (((quadsum 4) 3) 2) tiene un tipo diferente Intger -> Integer por ejemplo que ((((quadsum 4) 3) 2) 1) que tiene un tipo de Integer.

7

Creo que está malinterpretando el sistema de tipo Haskell.

En primer lugar, su función "quadsum" ya está currificada. Puede escribir "quadsum 4 3" y recuperar una función que espera el 2 y el 1 como argumentos adicionales. Cuando escribe "quadsum 4 3 2 1", eso es equivalente a "((((quadsum 4) 3) 2) 1)".

En Haskell una lista de enteros tiene un tipo diferente a un entero o una tupla como "(4,3,2,1)". Dado esto, es bastante difícil entender lo que estás tratando de hacer. ¿Qué se supone que pasará si escribes esto?

magicalFunctionMaker quadsum [5,4,3,2,1] 

Su "magicalFunctionMaker" parece más bien "foldl", excepto que la función que le das a foldl sólo toma dos argumentos. Para que pueda escribir:

mySum = foldl (+) 0 

Esto devuelve una función que toma una lista y suma los elementos.

(Por cierto, una vez que asimilar esto, aprender acerca de la diferencia entre foldl y foldr

Editar:.

Tener vuelva a leer su pregunta, creo que se está tratando de conseguir:

magicalFunctionMaker quadSum [4,3,2,1] :: Integer 
magicalFunctionMaker quadSum [4,3,2] :: Integer -> Integer 

Esto es imposible porque el tipo de magicalFunctionMaker dependería de la longitud del argumento de lista, lo que implicaría un tipado dinámico. Como dijo alguien, las funciones polivalentes pueden hacer algo parecido (aunque no con un argumento de lista), pero eso requiere qui Te algunos milli-olegs de tipo piratería.

+0

No lo haría implica tipeo dinámico; implicaría * tipado dependiente *. –

2

Ni siquiera se puede enumerar los casos de diferente longitud "manualmente":

mf f [] = f 
mf f [x] = f x 
mf f [x,y] = f x y 

--Occurs check: cannot construct the infinite type: t = t1 -> t 
--Probable cause: `f' is applied to too many arguments 
--In the expression: f x 
--In the definition of `mf': mf f [x] = f x 

Eso significa mf no puede tener una función de "aridad" arbitraria, usted tiene que decidir por uno. Por la misma razón, no puede convertir una lista arbitraria en una tupla: no puede decir cuántos elementos contendrá la tupla, pero el tipo de sistema necesita saber.

Puede haber una solución al limitar f a un tipo recursivo a = a -> a usando "forall" (ver http://www2.tcs.ifi.lmu.de/~abel/fomega/hm.html). Sin embargo, no pude hacerlo funcionar (parece que tengo que decirle a Leksah que use el flag -XRankNTypes en alguna parte), y esa restricción en f haría que tu función sea bastante inútil.

[editar]

Pensando, lo más parecido a lo que quiere es probablemente una especie de reducir la función. Como Pablo señaló, esto es similar a foldl, foldr ... (pero la versión a continuación es sin "argumento extra" y con un tipo homogéneo en comparación con foldl. Tenga en cuenta el caso base que falta para las listas vacías)

mf :: (a → a → a) -> [a] -> a 
mf f [x] = x 
mf f (x:y:xs) = mf f ((f x y) : xs) 
3

me ocurrió contra este mismo problema: Tengo una función como

someFunc :: Int -> Int -> Int -> Int 

Lo que me gustaría hacer es realizar una función mágica de tal manera que, por ejemplo

listApply :: [Int] -> (Int -> Int -> Int -> Int) -> Int 

tal que Puedo decir

listApply [1,2,3] someFunc 

Instintivamente, parece, y la respuesta de John está de acuerdo, que debería ser posible t o hacer algún tipo de sistema de magia para hacer esto. Existen soluciones a problemas similares que implican la creación de tipos de datos explícitamente irrevocables con un montón de despliegue y desenrollamiento explícitos (véase, por ejemplo, el capítulo 20 de Types and Programming Languages, o la 4ta publicación en this thread).

Hackeé la solución tipo por un tiempo; parece posible, pero no funcioné del todo antes de decidir probar Template Haskell, y allí las cosas son mucho más amistosas.

{-# LANGUAGE TemplateHaskell #-}  

import Language.Haskell.TH 
import Language.Haskell.TH.Syntax 

lApply :: [Int] -> String -> ExpQ 
lApply [] fn = return $ VarE (mkName fn) 
lApply (l:ls) fn = [| $(lApply ls fn) l |] 

(recuerde usar el pragma idioma o el interruptor de comandos -XTemplateHaskell.)

Para utilizar esta, se llama a lApply dentro de un empalme de este modo:

> $(lApply [1,2] "+") 
3 

Tenga en cuenta que tengo para usar una cadena que contiene el nombre de la función a la que quiero llamar: no puedo levantar una función directamente en un ExpQ, pero puedo referirme a su enlace global. Puedo ver cómo esto puede ser molesto. Además, debido a la forma en que atravesamos la lista, los argumentos deben presentarse en orden inverso en la lista.

Existen algunas otras arrugas: para generalizar esto a otros tipos de datos, esos tipos tienen que tener las instancias correspondientes en la clase Lift. Por ejemplo, no tiene una doble instancia, pero se puede hacer fácilmente una:

instance Lift Double where 
     lift x = return $ LitE (RationalL (toRational x)) 

El tipo de datos Lit No tiene un constructor DoubleL, pero RationalL se puede utilizar en su lugar, ya que va a empalmar un miembro general de la clase fraccional.

Si desea utilizar esto con las funciones que toman una combinación de tipos como argumentos, usted no será capaz de pasar una lista, ya que las listas no pueden ser de tipo mixto. Podrías usar tuplas para hacer esto, que honestamente no es mucho más difícil usando Template Haskell. En ese caso, harías una función que genera el AST de una función que toma una tupla con los tipos apropiados dentro y la asigna a la llamada de función que deseas. Alternativamente, puede envolver sus tipos de argumento dentro de un ADT apropiadamente diseñado, que por cierto también podría crear con Template Haskell. Esto se deja como ejercicio para el lector :)

Por último, se aplican todas las limitaciones estándar Plantilla Haskell. Por ejemplo, no puede llamar a esta función desde el módulo donde está definida debido a la restricción de etapa de GHC.

Plantilla Haskell es algo divertido e interesante, pero para ser completamente honesto, la solución de tipo de datos isoreccional es probablemente un poco más alto y obviamente no requiere el uso adicional de TH. Volveré y publicaré un seguimiento si/cuando funciono :)

1

Iba a editar mi otra publicación, pero esto es lo suficientemente grande como para que funcione.

He aquí una forma de hacerlo con "tipo mágico", pero me parece que es algo subóptimo, ya que requiere una función de elevación que es específica para funciones de un número particular de argumentos (más abajo).

Vamos a empezar por definir un tipo de datos recursiva

data RecT a = RecR a 
      | RecC (a -> RecT a) 

Así variables de tipo RecT puede ser o bien sólo el resultado envuelta (RecR) o pueden ser una recursividad continua (RecC).

Ahora, ¿cómo tomamos algo y lo convertimos en un tipo RecT a?

Los valores son fáciles:

valRecT x = RecR x 

RecR x es obviamente de tipo RecT a.

¿Qué pasa con una función que toma un argumento, como id?

idRecT x = RecC $ \x -> RecR x 

RecC envuelve una función que toma una variable y vuelve escriba un RecT. La expresión

\x -> RecR x 

es solo una de esas funciones, ya que como hemos observado anteriormente, RecR x es del tipo RecT a.

De manera más general, cualquier función de un argumento se puede levantar:

lift1RecT :: (a -> a) -> RecT a 
lift1RecT fn = RecC $ \a -> RecR $ fn a 

podemos generalizar esto, envolviendo varias veces la función más profundamente anidados llamadas dentro RecC:

lift2RecT :: (a -> a -> a) -> RecT a 
lift2RecT fn = RecC $ \b -> RecC $ \a -> RecR $ fn b a 

lift3RecT :: (a -> a -> a -> a) -> RecT a 
lift3RecT fn = RecC $ \c -> RecC $ \b -> RecC $ \a -> RecR $ fn c b a 

OK, por lo que He hecho todo este trabajo para convertir una función de un número arbitrario de argumentos en un solo tipo, RecT a. ¿Cómo usamos esto?

Podemos escribir fácilmente abajo un nivel de aplicación de función:

reduceRecT :: RecT a -> a -> RecT a 
reduceRecT (RecC fn) = fn 
reduceRecT _  = undefined 

En otras palabras, reduceRecT toma un argumento de tipo RecT una y otra de tipo A y devuelve una nueva RecT una que ha sido un nivel reducido .

También podemos desenrollar un cálculo terminada dentro de un RecT en el resultado:

unrollRecT :: RecT a -> a 
unrollRecT (RecR fn) = fn 
unrollRecT _  = undefined 

Ahora estamos listos para aplicar una lista de argumentos a una función!

lApply :: [a] -> RecT a -> a 
lApply [] fn = unrollRecT fn 
lApply (l:ls) fn = lApply ls $ (reduceRecT fn) l 

Consideremos el caso base primera: si hemos terminado con el cálculo, acabamos de desenvolver el resultado y lo devuelve.En el caso recursivo, reducimos la lista de argumentos en uno, luego transformamos el fn aplicando el encabezado de la lista al fn reducido, lo que da como resultado un nuevo RecT a.

Vamos a darle a esto un intento:

lApply [2,5] $ lift2RecT (**) 
> 32.0 

Así, ventajas y desventajas de este enfoque? Bueno, la versión de Template Haskell puede hacer una aplicación de lista parcial; esto no es cierto para la solución de tipo isorecursivo como se presenta aquí (aunque en principio podríamos arreglar esto con algo de fealdad). La solución tipo también tiene la desventaja de tener un código mucho más repetitivo asociado: necesitamos una listaNRecT para todas las N que queremos usar. Finalmente, es mucho menos fácil generalizar esto a la solución de tupla análoga si queremos aplicar funciones de tipos de variables mixtas.

Por supuesto, otra posibilidad interesante es mejorar la brevedad mediante el uso de Template Haskell para generar las funciones listRecT; esto elimina algunos repetitivos, pero en cierto sentido adquiere las desventajas de ambas implementaciones.

Cuestiones relacionadas