Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Requirements

  • 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.

Basic haskell-lookalike

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 of a <- f();, pat MUST be followed by =>, ,, =, if or in.
  • 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.

Apply function to monad

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.

Branch on parsed symbol

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
}

Rust-lookalike

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 of f without enough parameters, and might encourage attempts to use plain rust statements where expressions or ident ( ... ) is expected.

Apply function to error value

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),
    }
}

Branch on parsed symbol

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
}

Attempt at grammar

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.