-
Easy chaining of monadic actions.
Ie. separating two monadic actions should chain them together with
bind(a, |_| b)
. -
Simple bind of a value to an ident.
This should probably also allow any monadic action and not just calls to functions, eg. something like:
let a = m -> if other_var == b'a' { monadic_action_a(m) } else { monadic_action_b(m) }
-
Exposing the monad error inside of
mdo!
The purpose for this is to convert error types.
mdo!{m,
name <- take_while1(|c| c != b' ');
char(b' ');
surname <- take_while(|c| c != b'\n');
ret (name, surname)
}
Advantages:
- Does not look like pure rust, so allows us to use different semantics and make it clear that all in there is monaic interaction.
- Pretty clean, does not contain much noise.
Disadvantages:
- Does not allow for
pat
parts in the LHS ofa <- f();
,pat
MUST be followed by=>
,,
,=
,if
orin
. - Not obvious how to modify data in between calls (say apply some filter on
name
in the example above before passing it on to some function in the parser monad). - Due to an
ident
in the first position does not allow for arbitrary expressions in between parser invocations.
mdo!{m,
num <- integer();
char(b':');
// Lets replace the error if any:
m -> m.map_err(|_| MyError::FailedToParseIntegerHere);
bytes <- take(num);
// Some plain rust with monad state in m:
m -> match String::from_utf8(bytes) {
Ok(s) => ret(m, s),
Err(e) => err(m, MyError::StringParseError),
}
}
m -> expr;
syntax could be changed a bit to be more clear, but I do not know exactly what it should be. The expr
must return a a value in the monad.
mdo!{
c <- any();
char(b':');
r <- m -> match c {
b'a' => parse_a(m),
b'b' => parse_b(m),
_ => err(m, MyError::UnknownTag(c)),
}
char(b'\n');
ret r
}
mdo!{m,
let name = take_while(|c| c != b' ');
char(b' ');
let surname = take_while(|c| c != b'\n');
ret (name, surname)
}
Advantages:
- Allows for
pat
in LHS thanks to the use of=
for bind.
Disadvantages:
- A bit more wordy
- Might be confusing since stuff like
let a = f();
looks like an invocation off
without enough parameters, and might encourage attempts to use plain rust statements where expressions orident ( ... )
is expected.
Going to use the same syntax for inline rust-expressions:
mdo!{m,
let num = integer();
char(b':'), |_| MyError::FailedToParseIntegerHere;
let bytes = take(num);
// Some plain rust with monad state in m:
m -> match String::from_utf8(bytes) {
Ok(s) => ret(m, s),
Err(e) => err(m, MyError::StringParseError),
}
}
mdo!{
let c = any();
char(b':');
let r = m -> match c {
b'a' => parse_a(m),
b'b' => parse_b(m),
_ => err(m, MyError::UnknownTag(c)),
}
char(b'\n');
ret r
}
NAMED_ACTION := $ident '(' ($expr ',')* ')'
INLINE_ACTION := $ident '->' $expr
ACTION := INLINE_ACTION / NAMED_ACTION
VAR := $ident ':' $ty / $pat
BIND := 'let' VAR '=' ACTION
THEN := ACTION
RET_PLAIN := 'ret' $expr
RET_TYPED := 'ret' $typ ',' $typ ':' $expr
ERR_PLAIN := 'err' $expr
ERR_TYPED := 'err' $typ ',' $typ ':' $expr
RET := RET_TYPED / RET_PLAIN
ERR := ERR_TYPED / ERR_PLAIN
EXPR := ( BIND ';' / THEN ';' )* (RET / ERR / THEN)