2011-12-11 10 views
5

Supongamos que tengo alguna función genéricaProporcionar un cuerpo de función diferente para una función genérica basada en el tipo

genericFunc :: a -> b 
genericFunc x = doSomeHardWork 

Pero para un tipo particular, hay una manera mucho más eficiente que genericFunc se podría hacer.

genericFunc :: ParticularType -> b 
genericFunc x = doSomeEasyWork 

¿Cuál es la mejor manera de combinar estos dos cuerpos de las funciones en el mismo genericFunc, de tal manera que cuando se utiliza en ParticularType, se doSomeEasyWork, pero cuando se usa en otros tipos, se doSomeHardWork? Estoy excluyendo específicamente la opción de usar un nombre diferente o diferentes módulos.

Creo que esto se puede hacer con una clase de letra, pero estoy más interesado en las soluciones que usan pragmas de lenguaje. Tengo una vaga noción de que esto se puede hacer con lenguaje pragmático, pero no tengo idea de cómo. Puntos de bonificación si compara y contrasta estos enfoques y/o cualquier otro enfoque posible.

+0

vagamente inspirado por pensar en esta pregunta: [Si hay algo que no es una lista] (http://stackoverflow.com/questions/ 8463777/if-something-is-not-a-list-in-haskell) –

Respuesta

8

Esto se puede hacer con clases de tipo definiendo el método de uso general en la definición de clase y sobrescribiéndolo en una instancia. La función anulada siempre será utilizada.

class ContainsInt c where 
    toList :: c -> [Int] 

    -- generic function 
    elem :: Int -> c -> Bool 
    elem n x = Prelude.elem n (toList x) 

instance ContainsInt() where 
    toList _ = [] 

    -- Override the generic function for type() 
    elem _ _ = False 

Una alternativa admitida por GHC es utilizar una regla de reescritura. La regla de reescritura le dice a GHC que reemplace una expresión por otra siempre que sea posible. Si el reemplazo está mal escrito, no se realizará, por lo que puede usarlo para reemplazar una función por una versión especializada. La regla de reescritura está dada por un pragma {-# RULES #-}.

class ContainsInt c where 
    toList :: c -> [Int] 

elem :: ContainsInt c => Int -> c -> Bool 
elem n x = Prelude.elem n (toList x) 

-- Replace 'elem' by 'elemUnit' if it has the same type 
{-# RULES "elem()" forall. elem = elemUnit #-} 

elemUnit :: Int ->() -> Bool 
elemUnit _ _ = False 

reglas de reescritura se realizan a discreción del compilador, por lo que la función especializada pueden o no pueden ser llamados en cualquier situación dada. Por ejemplo, la reescritura puede depender de si el compilador decide inline una función:

foo :: ContainsInt c -> Int -> [c] -> [Bool] 
-- Must use the generic function 
foo n cs = map (elem n) cs 

useFoo :: Int -> [()] -> [Bool] 
-- If 'foo' is inlined and 'elem' is not inlined, then this function will contain a rewritable call to 'elem'. 
-- Otherwise rewriting cannot happen. 
useFoo n cs = foo n cs 
2

Con GHC, puede usar un RULES pragma.

{-# RULES "genericFunc/easy" genericFunc = doSomeEasyWork #-} 

Esto aplicará la regla de reescritura siempre que los tipos coincidan y use la implementación genérica de lo contrario.

+0

Siempre que _GHC pueda demostrar_ que los tipos coinciden. –

+1

¿Hay algunos buenos ejemplos de este "código real" que puedo ver? –

+1

No use reglas, use una clase de tipo. Esto es para lo que son las clases de tipos. – augustss

Cuestiones relacionadas