
Hi everyone, I have just started learning Haskell, it is a very interesting language. As a first project, I am building a brainfuck interpreter, but have hit a stumbling block: IO. My program source is located at http://hpaste.org/11219 My program handles the state of a brainfuck program by passing State(lists of ints) objects around. When I added my getIn fuction, however, it needed to return an IO State object. This broke the run function, as it returned a State. No problem, I thought, and changed run to return IO State. This broke my exec function, which calls run on each element in a list. As exec passes on the state returned by the run function to itself, it needs to take an IO State. This causes problems in the loop function, which calls exec on a subprogram. Because exec now takes IO State, loop also needs to. However, loop is called by run, so now run needs to take IO State. Now, all functions called by run need to take IO State. All this creates a whole load of functions with IO, even though it should not be necessary. What I would like to know, is how I can avoid this, possibly by modifying exec to just take a State. Whew! What a long-winded explanation! Any ideas? --Jamie P.S. Please ignore my long-winded list iteration functions -- I'm working on a cleaner version.

Am Freitag, 17. Oktober 2008 08:58 schrieb Jamie McCloskey:
Hi everyone, I have just started learning Haskell, it is a very interesting language. As a first project, I am building a brainfuck interpreter, but have hit a stumbling block: IO. My program source is located at http://hpaste.org/11219
My program handles the state of a brainfuck program by passing State(lists of ints) objects around. When I added my getIn fuction, however, it needed to return an IO State object. This broke the run function, as it returned a State. No problem, I thought, and changed run to return IO State. This broke my exec function, which calls run on each element in a list.
As exec passes on the state returned by the run function to itself, it needs to take an IO State. This causes problems in the loop function, which calls exec on a subprogram. Because exec now takes IO State, loop also needs to. However, loop is called by run, so now run needs to take IO State. Now, all functions called by run need to take IO State.
All this creates a whole load of functions with IO, even though it should not be necessary. What I would like to know, is how I can avoid this, possibly by modifying exec to just take a State.
exec [] st = return st exec (x:xs) st = do newSt <- run x st exec xs newSt or exec (x:xs) st = run x st >>= exec xs Once you're doing input or output, you're in IO, so run and exec must have type a -> b -> IO c, getIn and putOut must also live in IO, everything else needn't.
Whew! What a long-winded explanation!
Any ideas?
--Jamie
P.S. Please ignore my long-winded list iteration functions -- I'm working on a cleaner version.
Probably the tidiest (as if that was a criterion for a brainfuck interpreter) would be to rename your State to ProgramState (or whatever) and write the interpreter in StateT ProgramState IO , you'd have run :: Operator -> StateT ProgramState IO () run Add = modify myAdd run Minus = ... run Input = do n <- lift $ readLn modify (inHelper n) and exec = mapM_ run HTH, Daniel

On Fri, Oct 17, 2008 at 07:58:45PM +1300, Jamie McCloskey wrote:
All this creates a whole load of functions with IO, even though it should not be necessary. What I would like to know, is how I can avoid this, possibly by modifying exec to just take a State.
The short answer is: it IS necessary. Think about what exec does, for instance. It takes a Program and an initial State and does... what? Executes the program, which might result in some things getting printed to the screen or some input being read from the keyboard. That is, exec can have some I/O effects. Therefore, exec's return type must be an IO value. The same goes for all the other functions you mentioned. This is a feature, not a bug --- the type of a function tells you precisely whether it can possibly have any I/O effects. If the return type is (IO something), then it can; if the return type does not involve IO, then it is *guaranteed*[1] that it cannot have any I/O effects. -Brent [1] Except for that pesky unsafePerformIO. But you should never use that, unless you really absolutely know what you are doing and why.
participants (3)
-
Brent Yorgey
-
Daniel Fischer
-
Jamie McCloskey