a brainfuck monad
Here's a simple program written using this monad. See if you can guess what it might do:
import Control.Monad.BrainFuck demo :: String demo = brainfuckConstants $ \constants -> do add 31 forever constants $ do add 1 output
Here's the brainfuck code that
If you feed that into a brainfuck interpreter (I'm using
hsbrainfuck for my
testing), you'll find that it loops forever and prints out each character,
starting with space (32), in ASCIIbetical order.
The implementation is quite similar to the ASM monad. The main differences are that it builds a String, and that the BrainFuck monad keeps track of the current position of the data pointer (as brainfuck lacks any sane way to manipulate its instruction pointer).
newtype BrainFuck a = BrainFuck (DataPointer -> ([Char], DataPointer, a)) type DataPointer = Integer -- Gets the current address of the data pointer. addr :: BrainFuck DataPointer addr = BrainFuck $ \loc -> (, loc, loc)
Having the data pointer address available
allows writing some useful utility functions like this one,
which uses the
next (brainfuck opcode
prev (brainfuck opcode
-- Moves the data pointer to a specific address. setAddr :: Integer -> BrainFuck () setAddr n = do a <- addr if a > n then prev >> setAddr n else if a < n then next >> setAddr n else return ()
Of course, brainfuck is a horrible language, designed to be nearly impossible to use. Here's the code to run a loop, but it's really hard to use this to build anything useful..
-- The loop is only entered if the byte at the data pointer is not zero. -- On entry, the loop body is run, and then it loops when -- the byte at the data pointer is not zero. loopUnless0 :: BrainFuck () -> BrainFuck () loopUnless0 a = do open a close
To tame brainfuck a bit, I decided to treat data addresses 0-8 as constants, which will contain the numbers 0-8. Otherwise, it's very hard to ensure that the data pointer is pointing at a nonzero number when you want to start a loop. (After all, brainfuck doesn't let you set data to some fixed value like 0 or 1!)
I wrote a little
brainfuckConstants that runs a BrainFuck
program with these constants set up at the beginning.
It just generates the brainfuck code for a series of ASCII art fishes:
With the fishes^Wconstants in place, it's possible to write a more useful
loop. Notice how the data pointer location is saved at the beginning, and
restored inside the loop body. This ensures that the provided BrainFuck
action doesn't stomp on our constants.
-- Run an action in a loop, until it sets its data pointer to 0. loop :: BrainFuck () -> BrainFuck () loop a = do here <- addr setAddr 1 loopUnless0 $ do setAddr here a
I haven't bothered to make sure that the constants are really constant,
but that could be done. It would just need a Contaol.Monad.BrainFuck.Safe
module, that uses a different monad, in which
don't do anything when the data pointer is pointing at a constant.
Or, perhaps this could be statically checked at the type level, with
type level naturals. It's Haskell, we can make it safer if we want to. ;)
So, not only does this BrainFuck monad allow writing brainfuck code using crazy haskell syntax, instead of crazy brainfuck syntax, but it allows doing some higher-level programming, building up a useful(!?) library of BrainFuck combinators and using them to generate brainfuck code you'd not want to try to write by hand.
Of course, the real point is that "monad" and "brainfuck" so obviously belonged together that it would have been a crime not to write this.