Fun with RULES Pragma

Let's have some fun with GHC's Rewrite Rules. A feature that can be used for neat performance improvements via levering laws that one knows hold but can't easily express in haskell for performance gains. The archetypical example of that is probably {-# RULES "map/map" forall f g xs. map f (map g xs) = map (f.g) xs #-} - which makes us of the functor law specialized to lists to ensure we don't have to traverse the list twice.

But ... we aren't here for sensible performance improvements. We are here to have fun (and watch the world burn).
So let's make use of the pragma in a way that is NOT intended: Writing the whole program as a series of rules!

For a quick example of what can be done:
  {-# NOINLINE f #-}
  f x = x
  
  {-# RULES "f" f True = False #-}
  
  main = print (f True)
when compiled with optimisations on it will print False, otherwise it will print True. Which goes to show that RULES can change the logic... but how far can we go?
  {-# LANGUAGE TemplateHaskell #-} -- pulled in because I like that it can be used in rules
  
  {-# NOINLINE g #-}
  g = undefined
  
  {-# RULES "g" forall (a :: Int) (b :: Int). g a b = $([|a+b|]) #-}
  
  main = print ((g (2 :: Int) (3 :: Int)) :: Int)
jup that's right - it will print 5 with optimisations on. We have now managed to move part of our logic into the rules (and use TH in a silly way). Note that if we do not give all those types the rule will not fire, so careful with how you use them/rely on them! Here the "-ddump-rules" and "-ddump-simpl-stats" flags can help a lot.

but we are by far not done. There's still this pesky "print" outside! Let's move it in:
  {-# NOINLINE h #-}
  h = undefined
  
  {-# RULES "h" forall (a :: Int) (b :: Int). h a b = print $([|a+b|]) #-}
  main = (h (3 :: Int) (2 :: Int)) :: IO ()
much better. but there's still too much outside of rules for my liking. So let's get rid of it!
  {-# NOINLINE h #-}
  h = undefined
  
  {-# NOINLINE t #-}
  t = undefined
  
  {-# RULES "h" forall (a :: Int) (b :: Int). h a b = print $([|a+b|]) #-}
  {-# RULES "t" t = (h :: Int -> Int -> IO ()) 2 3 #-}
  
  main = t :: IO ()
but wait! there's still this ugly "t" to be found outside our RULES. But we can get rid of that as well.
  {-# NOINLINE h #-}
  h = undefined
  
  {-# NOINLINE t #-}
  t = undefined
  
  {-# RULES "h" forall (a :: Int) (b :: Int). h a b = print $([|a+b|]) #-}
  {-# RULES "t" t = (h :: Int -> Int -> IO ()) 2 3 #-}
  {-# RULES "finisher" main = t #-}
  
  main = undefined :: IO ()
much better isn't it? ... now if only we didn't have to define so many "undefined" helper functions...

  {-# LANGUAGE TemplateHaskell #-}
  
  {-# NOINLINE t #-}
  t = undefined
  
  {-# RULES "g" forall (a :: Int). t a = print a #-}
  {-# RULES "h2" forall (a :: Int) (b :: Int). t a b = t $([|a+b|]) #-}
  {-# RULES "t" t = (t :: Int -> Int -> IO ()) 2 3 #-}
  {-# RULES "finisher" main = t #-}
  
  main = undefined :: IO ()
we don't actually! We only need a single one and can do everything else via overload resolution! Obviously any type safety is gone by this point, but since sanity has left the building long ago anyway, I figure that's not a big deal.

So there you have it. RULES Programming! Much less restrictive than normal Haskell!

My Posts

Embed Haskell Funcs in Dynamic
Fun With RULES
Monad Tutorial
Infinite Type Part 1
Lisp Paper Recommendation