Suppose I have some generic function
genericFunc :: a -> b
genericFunc x = doSomeHardWork
But for a particular type, there is a much more efficient way that genericFunc
could be done.
genericFunc :: ParticularType -> b
genericFunc x = doSomeEasyWork
What is the best way to combine these two function bodies into the same genericFunc
, such that when used on ParticularType
, it will doSomeEasyWork
, but when used on other types, it will doSomeHardWork
? I'm specifically excluding the option of using a different name, or different modules.
I believe this can be done with a typeclass, but I am more interested in solutions that use language pragmas. I have a vague inkling that this can be done with language pragmas but I have no idea how. Bonus points if you compare and contrast these approaches, and/or any other possible approaches.
This can be done with type classes by defining the general-purpose method in the class definition and overriding it in an instance. The overridden function will always be used.
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
An alternative supported by GHC is to use a rewrite rule. The rewrite rule tells GHC to replace one expression by another whenever possible. If the replacement is ill-typed, it will not be done, so you can use this to replace a function by a specialized version. The rewrite rule is given by a {-# RULES #-}
pragma.
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
Rewrite rules are performed at the discretion of the compiler, so the specialized function may or may not be called in any given situation. For example, the rewrite may depend on whether the compiler decides to inline a function:
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
With GHC, you can use a RULES
pragma.
{-# RULES "genericFunc/easy" genericFunc = doSomeEasyWork #-}
This will apply the rewrite rule whenever the types match and use the generic implementation otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With