Expand description
Haskell-style functional programming for Rust.
hdo! turns a sequence of binds into a chain of monadic operations over
Option, Result and Vec. The README lists the combinators, type
classes and types that are 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));§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. |
§The block’s result
A block ends either in pure value, which lifts a plain value into the
block’s monad, or in a final expression without a semicolon, which is
already an Option, Result or Vec and is returned as it is:
use raskell::hdo;
let lifted: Option<i32> = hdo! {
x <- Some(10);
pure x + 1
};
let monadic: Option<i32> = hdo! {
x <- Some(10i32);
x.checked_add(1)
};
assert_eq!(lifted, monadic);The ; is what tells the two apart: a last statement that keeps it is an
action, which leaves the block without a result and fails to compile.
§Errors of different types
Plain <- keeps the error type as it is. Use <-? to convert it through
From, the way ? does:
use raskell::hdo;
#[derive(Debug, PartialEq)]
struct Inner;
#[derive(Debug, PartialEq)]
struct Outer;
impl From<Inner> for Outer {
fn from(_: Inner) -> Self {
Outer
}
}
fn inner() -> Result<i32, Inner> {
Err(Inner)
}
let result: Result<i32, Outer> = hdo! {
x <-? inner();
pure x * 2
};
assert_eq!(result, Err(Outer));§Refutable patterns
A refutable pattern filters an Option block:
use raskell::hdo;
let result = hdo! {
Some(x) <- Some(Some(10));
pure x * 2
};
assert_eq!(result, Some(20));In a Result block there is no obvious error to produce, so throw is
required:
use raskell::hdo;
#[derive(Debug, PartialEq)]
enum Error {
Missing,
}
let result: Result<i32, Error> = hdo! {
Some(x) <- Ok(None::<i32>) throw Error::Missing;
pure x * 2
};
assert_eq!(result, Err(Error::Missing));§Lists
A Vec bind is a list comprehension, and guard filters it:
use raskell::hdo;
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)]);§Type annotations
Rust infers the bound value’s type from the expression. When the expression
carries no type (Err(..) says nothing about the Ok side), annotate the
pattern:
use raskell::hdo;
#[derive(Debug, PartialEq)]
enum Error {
Failed,
Missing,
}
let result: Result<i32, Error> = hdo! {
Some(x): Option<i32> <- Err(Error::Failed) throw Error::Missing;
pure x * 2
};
assert_eq!(result, Err(Error::Failed));§Actions
A bare statement expression is sequenced, not discarded, so anything monadic in statement position short-circuits the block:
use raskell::hdo;
#[derive(Debug, PartialEq)]
struct Error;
fn check() -> Result<(), Error> {
Err(Error)
}
let result: Result<i32, Error> = hdo! {
check();
pure 1
};
assert_eq!(result, Err(Error));Bind it to _ with a plain let to opt out:
let mut empty: Vec<i32> = Vec::new();
let result = hdo! {
x <- Some(10);
let _ = empty.pop();
pure x
};
assert_eq!(result, Some(10));§Async
Wrapping the statements in async { .. } expands the block into an
async move block,
so .await is allowed anywhere inside it: in bind expressions, guard
conditions and pure alike. The result is a plain future: .await it, or
spawn it.
use raskell::hdo;
#[derive(Debug, PartialEq)]
enum ApiError {
Store,
TooSmall,
}
#[derive(Debug, PartialEq)]
struct StoreError;
impl From<StoreError> for ApiError {
fn from(_: StoreError) -> Self {
ApiError::Store
}
}
async fn fetch(value: i32) -> Result<i32, ApiError> {
Ok(value)
}
async fn from_store() -> Result<i32, StoreError> {
Err(StoreError)
}
let block = hdo!(async {
x <- fetch(10).await;
y <-? from_store().await;
guard x > 5 throw ApiError::TooSmall;
pure x + y
});
let result: Result<i32, ApiError> = pollster::block_on(block);
assert_eq!(result, Err(ApiError::Store));A final expression ends an async block the same way, .await included:
use raskell::hdo;
async fn fetch(value: i32) -> Option<i32> {
Some(value)
}
let block = hdo!(async {
x <- fetch(10).await;
fetch(x * 2).await
});
assert_eq!(pollster::block_on(block), Some(20));Futures are not awaited implicitly: write the .await yourself. The block’s
type has to be known, either from an annotation on the awaited value or from
the enclosing function’s signature.
§Limitations
Modules§
- prelude
- The common imports.
Macros§
- hdo
- Haskell-style
donotation overOption,ResultandVec.