This is an experimental feature. The parser is an interesting way to accumulate events from the stream. Imagine that event stream carries a tokens for some parser. And we can parse a single value from several successive values. We expect that our value is compound and we gradually take it bit by bit from the event stream.
In Haskell there are nice parser combinators libraries which
create very flexible parsers with instance to Applicative
.
In dyna
there is an experimental feature that provides this for event streams.
data Parser m a b = ...
Parser is some stateful accumulator function that expects
as input events of type a
and produces events of the type b
.
The simplest parser is headP
. It just takes the first element
of any stream:
headP :: Frp m => Parser m a a
Also we have maybeP
parser that skips every element
that results in Noting
but if the argument function produces
Just val
it takes that value and completes:
maybeP :: Frp m => (a -> Maybe b) -> Parser m a b
Running the parser
Once we have a parser we can apply it to the event stream in several ways. The simplest one is to apply the parser to the event stream:
runParser :: Frp m => Parser m a b -> Evt m a -> m (Maybe b)
It starts to consume events from the stream and parser accumulates the value until it is done. Let’s take a first input from the user:
runParser headP (foreverE $ once getLine)
Hello!
Just "Hello!"
Parser returns Just val
if it’s succeeded with parsing and Nothing
if the parser fails. If it was all that we can do it would
be very involved way to do the stuff.
Parser combinators
Parsers a great that we can build complex parser out of simple ones
with instance to Functor
and Applicative
.
The Functor
is obvious. It just maps over result when it’s parsed.
But Applicative
does more interesting stuff. It behaves like
Haskell parsec combinators.
Let’s take for example:
f <*> a
So it first applies to event stream the parser f
and result produces
the function a -> b
, and after that it applies the parser a
to the rest of the event stream and applies the function to the result of the parsing
with the second parser.
Let’s for example take a string and an integer from the user input:
> import Text.Read
> runParser ((,) <$> headP <*> maybeP (readMaybe @Int)) (forevers $ once getLine)
Hi
23
Just ("Hi",23)
Note that the second parser will skip all invalid input until the number will be typed by the user.
Accumulation of values on event streams
We can also use parsers to transform event streams. So the input will supply the building blocks for compound value and parser will produce event to the output when whole value will be constructed.
We have two functions for that:
takeP :: Frp m => Parser m a b -> Evt m a -> Evt m b
The takeP
will take only one compound value from the stream.
Once the value is constructed it shut downs the stream.
Also we can produce infinite stream of compound values as they got parsed
with cycleP
:
cycleP :: Frp m => Parser m a b -> Evt m a -> Evt m b
Let’s check it out in the ghci
:
> data Person = Person { name :: String, age :: Int } deriving (Show)
> parsePerson = liftA2 Person headP (maybeP readMaybe)
> :t parsePerson
parsePerson :: Frp m => Parser m String Person
> prints $ cycleP parsePerson (forevers $ once getLine)
Liza
10
Person {name = "Liza", age = 10}
John
5
Person {name = "John", age = 5}
Bob
Williams -- skipped by the integer parser
Rick -- also skipped
4 -- ** done: compound value can be returned as event
Person {name = "Bob", age = 4}
We have created a type for a Person
with name and age fields.
We also created a parser that reads required types:
> parsePerson = liftA2 Person headP (maybeP readMaybe)
> :t parsePerson
parsePerson :: Frp m => Parser m String Person
As the last step we run cyclic parsing of the compound person value from the input stream and echo-print the results:
> prints $ cycleP parsePerson (foreverE $ once getLine)
We can find another example of parser application in the
file dyna/examples/InputForm.hs
in source code repo.
The parsers are experimental so far. It was cool to try out the ideas from the parser combinators and apply them to the event streams.
<=
How to make bindings=>
Conclusion- Up: Table of contents