Recall our Scan type was> module MarsRoverStateMonad where
> import Control.Monad.State
... other imports and data/instance definitions as before ...
This type looks exactly like the type for the State monad, and that documentation page reveals that parsing is very well handled by the
> -- type Scan = String → (a, String)
StateT String  atype. Let's transform our Scan type into a monad:
Since, also, everything in the Position data type derives the class Read it becomes a simple matter to constrain our polymorphic type (
> type Scan a = StateT String  a
a) to the values we wish to obtain from our parser, like so:
This function,> next :: Read a ⇒ Scan a
> next = get >>= λ(x:xs) . put xs >> if x ≡ ' '
> then next
> else return (read [x])
next, is very simple for two reasons: first, the only separator that concerns us in this command language is the space (ASCII 32), and second, the tokens it consumes are only one character each. Both of these simplicities in this language can be easily and obviously enhanced when implementing scanner-parsers for richer languages.
With this new scanner (
next) based on the StateT monad transformer, we can now rewrite our lifting functions in the monadic style. This frees us to concentrate on the token at the current position and lets the monad handle the stream positioning.
Note that the abstraction of the type signatures of these functions did not change at all, but the implementations simplified significantly (that is, if you consider the monadic style a simplification over the previous implementation that uses let-bindings to sequence computations).> liftLoc = next >>= λx . next >>= λy . return (Loc x y)
> liftOrient = next
> liftPos = liftLoc >>= λloc .
> liftOrient >>= λdir . return (Pos loc dir)
Now to run the system, we replace in
let (start, _) = liftPos pos
and we're done!
let start = head $ evalStateT (liftPos) pos
I like the monadic threading much, much, better than the previous implementation that manually threaded the position of the input stream via tupling using let-bindings, but this version here also leaves something to be desired. Note the parsing functions (
liftLoc) both follow the the format of:
I don't like that I need to identify and bind the variables
grabA >>= λa . grabB >>= λb . return (Τ a b)
b; I don't like that one bit! In my eyes, identifying these variables does nothing to make the program more declarative. I would much rather write these functions in the following manner...
...with the my weird monadic stream operators defined (obviously) as follows...> liftLoc = next >>=> next >=>> Loc
> liftPos = liftLoc >>=> liftOrient >=>> Pos
...but then I worry that this syntax is too esoteric for its own good. Thoughts?> (>>=>) :: Monad m ⇒ m a → m b → m (a, m b)
> mA >>=> mB = mA >>= λa . return (a, mB)
> (>=>>) :: Monad m ⇒ m (a, m b) → (a → b → c) → m c
> mTup >=>> τ = mTup >>= λ(a, mB) .
> mB >>= λb . return (τ a b)
Yes, a person leaving a remark pointed out that I've reinvented
liftM2. Obviously so, in retrospect. I'm on a roll! After a year of entries, I suppose I'll have reinvented most of Haskell's standard library.
Actually for binary situations (that is, for types that take two arguments from the stream to construct), I think I will use these special streaming monadic operators, but when types become more complex, then more, and more complex, operators are required (each new argument for a type doubles the complexity of each new operator). This tells me (as it has already told Uustalu and Vene) that monads are not the best way to go about scanning/parsing streams to extract an internal representation of the grammar.