2011-06-26 11 views
11

¿Es posible emular una función con su propio tipo de datos con alguna extensión GHC? Lo que quiero hacer es, por ejemplo,¿Es posible emular una función usando su propio tipo de datos?

(sintaxis imaginario)

data MyFunc = MyFunc String (Int->Int) 

instance (Int->Int) MyFunc where 
    ($) (MyFunc _ f) i = f i 

inc = MyFunc "increment" (1+) 

test = inc 1

es decir datos que llevan consigo metainformación y pueden ajustarse a patrones, pero que todavía se pueden llamar como una función regular. Ahora, sé que podría definir mi propio operador de infijo como $$ y llamar al inc $$ 1, pero ser capaz de usar la sintaxis de llamada a la función normal sería muy útil en las DSL incorporadas.

Respuesta

18

Sí, se puede hacer de forma limitada.

Pero primero tendremos que

{-# LANGUAGE Rank2Types #-} 

Definamos

data M a b = M { name :: Int -> String -> String, eval :: a -> b } 

estoy añadiendo más estructura a sus nombres para que pueda obtener el apoyo más agradable espectáculo. ;)

luego le permite definir una clase:

class Magic m where 
    magic :: M a b -> m a b 

instance Magic M where 
    magic = id 

instance Magic (->) where 
    magic (M _ f) = f 

Ahora, considere el tipo:

type MyFunc a b = forall m. Magic m => m a b 

El tipo de resultado de magic es bien (a -> b) o una M a b.

Puede usarse como miembro de MyFunc. Ahora bien, este tipo es algo insatisfactorio, porque no se puede crear instancias despachan en él, pero sí quiere decir que

inc :: MyFunc Int Int 
inc = magic (M (const (showString "inc")) (+1)) 

test :: Int 
test = inc 1 

funciona bien.

Incluso podemos hacer una forma bastante agradable de mostrarlos. Aunque no podemos usar show en MyFunc, podemos definirlo para M.

instance Show (M a b) where 
    showsPrec d (M s _) = s d 

Entonces podemos hacer una función podemos aplicar a M a b (y por extensión cualquier MyFunc) para salir de una M a b.

m :: M a b -> M a b 
m = id 

y podemos definir un combinador especial para mostrar MyFunc s:

showM :: MyFunc a b -> String 
showM f = show (m f) 

entonces podemos jugar. Podemos definir composiciones de MyFunc s.

infixr 9 .# 
(.#) :: MyFunc b c -> MyFunc a b -> MyFunc a c 
f .# g = magic (M 
    (\d -> showParen (d > 9) $ showsPrec 10 (m f) . 
           showString " . " . 
           showsPrec 9 (m g)) 
    (f . g)) 

inc2 :: MyFunc Int Int 
inc2 = inc .# inc 

test2 :: Int 
test2 = inc2 1 

bar, baz :: String 
bar = showM inc 
baz = showM inc2 

Y porque di suficiente estructura para los nombres, que incluso llegar parentización correcta para composiciones más complicadas, sin paréntesis innecesarios.

*Main> showM $ inc2 .# inc 
"(inc . inc) . inc" 

*Main> showM $ inc .# inc2 
"inc . inc . inc" 

Pero recuerde, usted no será capaz de definir todas las instancias de MyFunc, ya que sólo puede ser un type, y no un newtype. Para definir las instancias, tendrá que definirlas en M, y luego usar m para convertirlas a ese tipo, de modo que el despacho implícito tenga un tipo al que agarrarse.

Debido al tipo de rango 2, si utiliza estos en gran medida en contextos locales, es posible que también desee activar NoMonoLocalBinds y/o NoMonomorphismRestriction.

+9

Esto es un poco aterrador. Lo amo. –

5

No, la sintaxis f e no se puede sobrecargar. El f tiene que tener el tipo S -> T.

Pero aún puede hacer mucho con EDSL si realiza una incrustación profunda, es decir, deja que sus funciones creen árboles de sintaxis en lugar de informática.

3

No se puede sobrecargar directamente la sintaxis de la llamada a la función, no.

Usted puede haga su propio tipo de flecha personalizada --- vea Control.Arrow. Luego puede invocar sus "funciones" usando arrow notation (deberá incluir en la parte superior de su archivo fuente). Si esto es lo suficientemente bueno para usted depende de las necesidades de su DSL.

+0

++ 1 para las flechas. – fuz

+0

¡No te olvides de los aplicativos! También tienen un operador de aplicaciones. – luqui

Cuestiones relacionadas