forward :: Tape -> Tape
backward :: Tape -> Tape
modify :: (Word8 -> Word8) -> Tape -> Tape
value :: Tape -> Word8
Now that we have the data types out of the way: it's good that you have some type signatures, but it's a shame that you left them at some point. And the use of unsafeCoerce
is really unsafe there. A Char
does not consists of a single byte, a Char
is a unicode character. You can use fromIntegral . fromEnum
, although you still want to handle those cases where the character value exceeds 255.
So we now have a Program
, a Tape
and all of that. Those are alternative solutions. So, why do I even propose them? Because they make your other functions easier. Let us have a look at pu
:
pu :: State -> Input -> (State, Output)
pu state@(State memL memR@((MemCell cell):tMemR) (Position pos)) (instruc, inpt) = (state', (out, cmd))
where state' = State memL' memR' (Position pos')
pos' = 0 -- Dummy
memR' | instruc == incB = (MemCell (cell + 1)) : tMemR
| instruc == decB = (MemCell (cell - 1)) : tMemR
| instruc == rd = (MemCell inpt) : tMemR
| instruc == incP = tMemR
| instruc == decP = (head memL) : memR
| otherwise = memR
memL' | instruc == incP = (MemCell cell) : memL
| instruc == decP = tail memL
| otherwise = memL
out | instruc == prnt = Just (chr . fromIntegral $ cell)
| otherwise = Nothing
cmd | instruc == moveR = Jump R
| instruc == moveL = Jump L
| otherwise = Continue
There's a lot going on there. Now, if we use our Tape
and BFInstruction
, the code gets tremendously easier:
pu :: BFInstruction -> Tape -> String -> (String, String, Tape)
pu i t input =
case i of
BFInc -> taped $ modify (+1) t
BFDec -> taped $ modify (subtract 1) t
BFPrev -> taped $ backward t
BFNext -> taped $ forward t
BFGet -> ("", tail input, modify (const (head input)) tape) -- type error here, but easy to fix
BFPut -> (value tape, input, tape) -- type error, easy to fix
BFLoop p | value t /= 0 -> runLoop p
_ -> taped t
where
taped t' = ("", input, t') -- no output, no input consumption
Instead of guards with ==
, you now compare to data constructors. This has the nice side-effect that the compiler can now warn you if you forgot an instruction. And all just because we went from String
to [BFInstruction]
. We eliminated a bunch of mistakes that can happen there. Note that I left runLoop
out since it is a little bit difficult to write with those types; it's a lot easier if we used another data type. But it's possible with (String, String, Tape)
.