Friday, August 29, 2008

Scanner-parsers II: State Monad Transformers

Okay, let's implement the same Mars rover command language with pretty much the same solution, but this time, for our scanner-parser of the Position, we're going to use the StateT monad transformer over the list monad to erase the housekeeping of the input stream.
> module MarsRoverStateMonad where

> import Control.Monad.State

... other imports and data/instance definitions as before ...
Recall our Scan type was
> -- type Scan = String → (a, String)
This type looks exactly like the type for the State monad, and that documentation page reveals that parsing is very well handled by the StateT String [] a type. Let's transform our Scan type into a monad:
> type Scan a = StateT String [] a
Since, also, everything in the Position data type derives the class Read it becomes a simple matter to constrain our polymorphic type (a) to the values we wish to obtain from our parser, like so:
> next :: Read a ⇒ Scan a
> next = get >>= λ(x:xs) . put xs >> if x ≡ ' '
> then next
> else return (read [x])
This function, 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.
> liftLoc = next >>= λx . next >>= λy . return (Loc x y)
> liftOrient = next
> liftPos = liftLoc >>= λloc .
> liftOrient >>= λdir . return (Pos loc dir)
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).

Now to run the system, we replace in runRovers the
let (start, _) = liftPos pos
with
let start = head $ evalStateT (liftPos) pos
and we're done!

Critique

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 (liftPos and liftLoc) both follow the the format of:
grabA >>= λa . grabB >>= λb . return (Τ a b)
I don't like that I need to identify and bind the variables a and 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...
> liftLoc = next >>=> next >=>> Loc
> liftPos = liftLoc >>=> liftOrient >=>> Pos
...with the my weird monadic stream operators defined (obviously) as follows...
> (>>=>) :: 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)
...but then I worry that this syntax is too esoteric for its own good. Thoughts?

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.

8 comments:

meteficha said...

import Control.Monad

liftLoc = liftM2 Loc next next
liftPos = liftM2 Pos liftLoc liftOrient

geophf said...

@meteficha, point well taken and very nicely done. You caught me in the act of unconsciously reinventing liftM2. Here's your ⊥-trophy. I suppose after a year of blogging, I'll have reinvented most of the standard library. *sigh*

However, your solution, albeit nicely done and elegant, is not at all to my taste; I guess I have the sequencing of DCGs ingrained into me. Also both our solutions handle the binary case fine, but the general case adds complexity for each new argument to be parsed, no?

urso said...
This post has been removed by the author.
urso said...

idea from applicative... but StateT not instance of applicative

mf <*> mx = do f <- mf
x <- mx
return $ f x

liftLoc = return Loc <*> next <*> next

liftPos = return Pos <*> liftLoc <*> liftOrient

-- or use ap:
liftLoc = return Loc `ap` next `ap` next
liftPos = return Pos `ap` liftLoc `ap` liftOrient

Erik said...

Or even better, go completely for Applicative Functors:

liftLoc = Loc <$> next <*> next

ghkj said...

Joy in warcraft leveling living comes wow lvl from having wow lvl fine emotions,wow power level trusting them,power leveling giving them power leveling the freedom of wrath of the lich king power leveling a bird in the open.wlk power leveling Joy in living can age of conan gold never be assumed as a pose,or put on from guildwars gold the outside as a mask. People who have this joy don not need maple story mesos to talk about it; they radiate it. wow gold They just live out their joy and let wow power leveling it splash its sunlight and glow into other lives as naturally as bird sings.

xuemei said...

Now do you worried about that in the game do not had enough aion kina to play the game, now you can not worried, my friend told me a website, in here you can buy a lot aion online kina and only spend a little money, do not hesitate, it was really, in here we had much aion gold, we can sure that you will get the cheap aion kina, quick to come here to buy aion kina.

Now do you worried about that in the game do not had enough aion kina to play the game, now you can not worried, my friend told me a website, in here you can buy a lot aion online kina and only spend a little money, do not hesitate, it was really, in here we had much aion gold, we can sure that you will get the cheap aion kina, quick to come here to buy aion kina.

Adi said...

Oes Tsetnoc Seo Contest one of the ways in which we can learn seo besides Upaya Mengembalikan Jati Diri Bangsa. By participating in the Oes Tsetnoc or Mengembalikan Jati Diri Bangsa we can improve our seo skills. To find more information about Oest Tsetnoc please visit my Oes Tsetnoc pages. And to find more information about Mengembalikan Jati Diri Bangsa please visit my Mengembalikan Jati Diri Bangsa page and other update like as Beratnya Mengembalikan Jati Diri Bangsa, Mengembalikan Jati Diri Bangsa di perpanjang and Jangan Berhenti Mengembalikan Jati Diri Bangsa. Thank you So much.

Oes Tsetnoc | Lanjutkan Mengembalikan Jati Diri Bangsa