raskell 0.1.1

Haskell-style functional programming for Rust
Documentation
//! Blocks that end in a monadic expression instead of `pure`.

use pollster::block_on;
use raskell::hdo;

#[derive(Debug, PartialEq)]
enum StoreError {
    Missing,
}

#[derive(Debug, PartialEq)]
enum ApiError {
    Store,
    TooSmall,
}

impl From<StoreError> for ApiError {
    fn from(_: StoreError) -> Self {
        ApiError::Store
    }
}

fn double(value: i32) -> Result<i32, ApiError> {
    Ok(value * 2)
}

fn missing() -> Result<i32, ApiError> {
    Err(ApiError::TooSmall)
}

async fn load(value: i32) -> Result<i32, StoreError> {
    if value > 0 {
        Ok(value)
    } else {
        Err(StoreError::Missing)
    }
}

async fn double_async(value: i32) -> Result<i32, ApiError> {
    double(value)
}

#[test]
fn option_tail() {
    let result: Option<i32> = hdo! {
        a <- Some(10);

        Some(a + 5)
    };

    assert_eq!(result, Some(15));
}

#[test]
fn option_tail_can_fail() {
    let result: Option<i32> = hdo! {
        a <- Some(10);

        u32::try_from(a - 100).ok().map(|value| value as i32)
    };

    assert_eq!(result, None);
}

#[test]
fn option_tail_short_circuits_before_running() {
    let result: Option<i32> = hdo! {
        a <- None::<i32>;

        Some(a + 5)
    };

    assert_eq!(result, None);
}

#[test]
fn result_tail() {
    let result: Result<i32, ApiError> = hdo! {
        a <- double(3);

        double(a)
    };

    assert_eq!(result, Ok(12));
}

#[test]
fn result_tail_propagates_its_error() {
    let result: Result<i32, ApiError> = hdo! {
        a <- double(3);

        let _ = a;

        missing()
    };

    assert_eq!(result, Err(ApiError::TooSmall));
}

#[test]
fn list_tail() {
    let result: Vec<i32> = hdo! {
        a <- vec![1, 2];

        vec![a, a * 10]
    };

    assert_eq!(result, vec![1, 10, 2, 20]);
}

#[test]
fn list_tail_can_be_empty() {
    let result: Vec<i32> = hdo! {
        a <- vec![1, 2, 3];

        if a % 2 == 0 { vec![a] } else { Vec::new() }
    };

    assert_eq!(result, vec![2]);
}

#[test]
fn tail_follows_every_other_statement() {
    let result: Result<i32, ApiError> = hdo! {
        a <- double(4);

        let b = a + 1;

        guard b > 5 throw ApiError::TooSmall;

        double(3);

        double(b)
    };

    assert_eq!(result, Ok(18));
}

#[test]
fn tail_after_a_refutable_bind() {
    let result: Option<i32> = hdo! {
        Some(a) <- Some(Some(10));

        Some(a * 2)
    };

    assert_eq!(result, Some(20));
}

#[test]
fn tail_after_a_typed_bind() {
    let result: Option<i32> = hdo! {
        a: i32 <- Some(10);

        Some(a * 2)
    };

    assert_eq!(result, Some(20));
}

#[test]
fn tail_after_a_try_bind() {
    let result: Result<i32, ApiError> = hdo! {
        a <-? Err::<i32, StoreError>(StoreError::Missing);

        double(a)
    };

    assert_eq!(result, Err(ApiError::Store));
}

#[test]
fn pure_still_works() {
    let result: Option<i32> = hdo! {
        a <- Some(10);
        b <- Some(20);

        pure a + b
    };

    assert_eq!(result, Some(30));
}

#[test]
fn pure_with_a_trailing_semicolon_still_works() {
    let result: Option<i32> = hdo! {
        a <- Some(10);

        pure a * 2;
    };

    assert_eq!(result, Some(20));
}

#[test]
fn async_tail() {
    let result: Option<i32> = block_on(hdo!(async {
        a <- Some(10);

        Some(a + 5)
    }));

    assert_eq!(result, Some(15));
}

#[test]
fn async_tail_awaits() {
    let result: Result<i32, ApiError> = block_on(hdo!(async {
        id <-? load(21).await;

        double_async(id).await
    }));

    assert_eq!(result, Ok(42));
}

#[test]
fn async_tail_propagates_its_error() {
    let result: Result<i32, ApiError> = block_on(hdo!(async {
        id <-? load(1).await;

        double_async(id).await;

        missing()
    }));

    assert_eq!(result, Err(ApiError::TooSmall));
}

#[test]
fn async_short_circuits_before_the_tail() {
    let result: Result<i32, ApiError> = block_on(hdo!(async {
        id <-? load(-1).await;

        double_async(id).await
    }));

    assert_eq!(result, Err(ApiError::Store));
}

#[test]
fn async_pure_still_works() {
    let result: Result<i32, ApiError> = block_on(hdo!(async {
        id <-? load(20).await;

        guard id > 5 throw ApiError::TooSmall;

        pure id + 1
    }));

    assert_eq!(result, Ok(21));
}