raskell 0.1.1

Haskell-style functional programming for Rust
Documentation

raskell

Haskell-style functional programming for Rust: combinators, type classes and types.

Implemented: hdo!, do notation over Option, Result and Vec, with an async mode. See the roadmap for what is planned.

use raskell::hdo;

let result = hdo! {
    x <- Some(10);
    y <- Some(20);

    let sum = x + y;

    guard sum > 5;

    pure sum * 2
};

assert_eq!(result, Some(60));

hdo! chains binds over Option, Result and Vec.

Statements

Syntax Meaning
pattern <- expression; Bind. Short-circuits on None / Err, iterates over a Vec.
pattern <-? expression; Bind a Result, converting the error through From.
pattern <- expression throw error; Bind a Result whose pattern may fail to match.
pattern: Type <- expression; Bind with the bound value's type spelled out.
guard condition; Yield None (or drop the list element) unless condition holds.
guard condition throw error; Yield Err(error) unless condition holds.
let pattern[: Type] = expression; Plain let, no monad involved.
expression; An action, sequenced like a bind whose value is discarded.
pure expression Lifts a plain value into the block's monad. Must come last.
expression A final expression without ;, already monadic. Must come last.

A block has to end in a result: either pure value, which lifts a plain value, or a final expression without a semicolon, which is already an Option, Result or Vec and is returned as it is.

let lifted: Option<i32> = hdo! {
    x <- Some(10i32);

    pure x + 1              // an i32, so `pure` wraps it
};

let monadic: Option<i32> = hdo! {
    x <- Some(10i32);

    x.checked_add(1)        // already an Option<i32>, returned as it is
};

A last statement that keeps its ; is still an action, so the block is missing its result and fails to compile.

Errors of different types

let result: Result<i32, Outer> = hdo! {
    x <-? inner();   // inner() -> Result<i32, Inner>, Outer: From<Inner>

    pure x * 2
};

Lists

let pairs: Vec<(i32, i32)> = hdo! {
    x <- vec![1, 2, 3];
    y <- vec![10, 20];

    guard x + y > 12;

    pure (x, y)
};

assert_eq!(pairs, vec![(1, 20), (2, 20), (3, 10), (3, 20)]);

Async

Wrapping the statements in async { .. } expands the block into an async move block, so .await works anywhere inside it. The result is a plain future: .await it or spawn it.

let result: Result<i32, ApiError> = hdo!(async {
    x <- fetch(10).await;
    y <-? from_store().await;      // ApiError: From<StoreError>

    guard x > 5 throw ApiError::TooSmall;

    pure x + y
})
.await;

A final expression works there too, .await included:

let user: Result<User, Error> = hdo!(async {
    id <-? get_id().await;

    fetch_user(id).await
})
.await;

Futures are not awaited implicitly; write the .await yourself.

Limitations

  • An async block binds Option and Result only: a list bind cannot short-circuit a future.
  • Lists are bound as Vec; other iterators need .collect() first.

See the crate documentation for the full reference.

Roadmap

Functions and combinators

map, filter, fold, compose, curry, flip, zip_with, maybe, either.

Type classes, Rust-style

Functor, Applicative, Monad, Foldable, Traversable, Semigroup, Monoid, as plain traits that compose with Iterator and the std traits.

Once Monad exists, hdo! should be defined in terms of it instead of the per-type HdoBind impls it dispatches on today.

Types

Either, NonEmpty, later Reader, State, Writer.

License

MIT