Haskell-style monads in Rust.

Overview

Monads, Functors, & More in Stable Rust

Haskell-style monads with macro-free Rust syntax.

Types work with zero annotations

A fancy example (that compiles and runs as a test in gallery.rs):

assert_eq!(
    vec![
        || "this string isn't a number",
        || "67",
        || "that was, but not binary",
        || "1010101010",
        || "that was, but more than 8 bits",
        || "101010",
        || "that one should work!",
        || panic!("lazy evaluation!"),
    ]
    .fmap(|x| move || u8::from_str_radix(x(), 2).ok())
    .asum(),
    Some(42)
);

Desugared do-notation:

assert_eq!(
    list![1..20]
        >> |x| {
            list![{ x }..20]
                >> |y| {
                    list![{ y }..20]
                        >> |z| guard::<List<_>>(x * x + y * y == z * z) >> |_| list![(x, y, z)]
                }
        },
    list![
        // Including "non-primitive" (i.e. multiples of smaller) triples
        (3, 4, 5),
        (5, 12, 13),
        (6, 8, 10),
        (8, 15, 17),
        (9, 12, 15)
    ]
);

The logic of Haskell lists with the speed of Rust vectors:

use rsmonad::prelude::*;
let li = list![1, 2, 3, 4, 5];
fn and_ten(x: u8) -> List<u8> { list![x, 10 * x] }
assert_eq!(li >> and_ten, list![1, 10, 2, 20, 3, 30, 4, 40, 5, 50]);

N-fold bind without type annotations:

// from the wonderful Haskell docs: https://en.wikibooks.org/wiki/Haskell/Understanding_monads/List
fn bunny(s: &str) -> List<&str> {
    list![s, s, s]
}
assert_eq!(
    list!["bunny"] >> bunny,
    list!["bunny", "bunny", "bunny"],
);
assert_eq!(
    list!["bunny"] >> bunny >> bunny,
    list!["bunny", "bunny", "bunny", "bunny", "bunny", "bunny", "bunny", "bunny", "bunny"],
);

And even the notoriously tricky join-in-terms-of-bind with no type annotations necessary:

let li = list![list![0_u8]]; // List<List<u8>>
let joined = li.join();      // -->  List<u8>!
assert_eq!(joined, list![0]);

Syntactic sugar

  • Rust requires >>= to be self-modifying, so we use >> instead. return (keyword) becomes consume and Haskell's >> becomes &.
  • Function-application order is standardized across the board to match >>= instead of <$>: it's x.fmap(f) to mean f <$> x.
    • I think this makes it easier to read as a chain of computations, but I'm not dead-set on it, and the type system would work either way.
  • For functors, you can use fmap(f, x) or x.fmap(f), or you can pipe it: x % f % g % ....
  • Applicative uses * instead of <*> (or the explicit method tie).
  • Alternative uses | instead of <|> (or the explicit method either).
  • Monoids use + instead of <>.

Use

Just write a monad! { ... and you get all its superclasses (Functor, Applicative, Alternative, ...) for free, plus property-based tests of the monad laws:

use rsmonad::prelude::*;

enum Maybe<A> {
    Just(A),
    Nothing,
}

monad! {
    Maybe<A>:

    fn consume(a) {
        Just(a)
    }

    fn bind(self, f) {
        match self {
            Just(a) => f(a),
            Nothing => Nothing,
        }
    }
}

// And these just work:

// Monad
assert_eq(Just(4) >> |x| u8::checked_add(x, 1).into(), Just(5));
assert_eq(Nothing >> |x| u8::checked_add(x, 1).into(), Nothing);
assert_eq(Just(255) >> |x| u8::checked_add(x, 1).into(), Nothing);

// Functor
assert_eq!(Just(4) | u8::is_power_of_two, Just(true));
assert_eq!(Nothing | u8::is_power_of_two, Nothing);

Rust-specific monads

Catch panics without worrying about the details:

fn afraid_of_circles(x: u8) -> BlastDoor<()> {
    if x == 0 { panic!("aaaaaa!"); }
    Phew(())
}
assert_eq!(
    Phew(42) >> afraid_of_circles,
    Phew(())
);
assert_eq!(
    Phew(0) >> afraid_of_circles,
    Kaboom,
);

Sharp edges

Right now, you can use >> for bind only when you have a concrete instance of Monad like Maybe but not a general <M: Monad<A>>. The latter still works but requires an explicit call to m.bind(f) (or, if you don't use the trait, Monad::<A>::bind(m, f)). This should be fixed with the Rust's non-lifetime binder feature when it rolls out.

"Cannot find type ... in this scope" in a doctest

Doctests try to guess where to place a fn main { ... } if you don't provide one, and sometimes it reads an rsmonad macro as something that should be in a main block. Try adding an explicit fn main () { ... } around the statements you want to run. If you don't want fn main() { ... } to show up in documentation but can't fix this error, comment it out:

/// monad! { ... }
/// # fn main {
/// a();
/// b();
/// c();
/// # }

#![no_std]

Disable default features:

# Cargo.toml

[dependencies]
rsmonad = { version = "*", default-features = false }

Note that this will also disable List,though this is probably what you want: we can't know its length at compile time (that's the point of its bind implementation), so it requires a heap. An alloc feature is in the works for #![no_std] extern crate alloc; crates, but it's not finalized yet.

You might also like...
Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]
Learn to write Rust procedural macros [Rust Latam conference, Montevideo Uruguay, March 2019]

Rust Latam: procedural macros workshop This repo contains a selection of projects designed to learn to write Rust procedural macros — Rust code that g

The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language.

rcc The Rust Compiler Collection is a collection of compilers for various languages, written with The Rust Programming Language. Compilers Language Co

Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed.

integra8 Integra8 rust integration test framework Rust with a focus on productivity, extensibility, and speed. | This repo is in a "work in progress"

Neofetch but in Rust (rust-toml-fetch)
Neofetch but in Rust (rust-toml-fetch)

rtfetch Configuration Recompile each time you change the config file logo = "arch.logo" # in src/assets. info = [ "", "", "yellow{host_n

Rust Sandbox [code for 15 concepts of Rust language]

Rust-Programming-Tutorial Rust Sandbox [code for 15 concepts of Rust language]. The first time I've been introduced to Rust was on January 2022, you m

TypeRust - simple Rust playground where you can build or run your Rust code and share it with others

Rust playground Welcome to TypeRust! This is a simple Rust playground where you can build or run your Rust code and share it with others. There are a

Rust Imaging Library: A high-level Rust imaging crate.

ril Rust Imaging Library: A performant and high-level Rust imaging crate. Documentation • Crates.io • Discord What's this? This is a Rust crate design

In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.
In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang.

Learn Rust What is this? In this repository you can find modules with code and comments that explain rust syntax and all about Rust lang. This is usef

Game Boy Emulator written in Rust, as a way to fully grasp the Rust programming language

Flan's Game Boy Emulator Game Boy Emulator written in Rust, as a way to get hands-on with the Rust programming language, and creating a proper project

Owner
Will Sturgeon
CS+CogSci @ UPenn
Will Sturgeon
A tool that helps you to turn in one command a Rust crate into a Haskell Cabal library!

cabal-pack A tool that helps you to turn in one command a Rust crate into a Haskell Cabal library! To generate bindings, you need to annotate the Rust

Yvan Sraka 18 Dec 31, 2022
Leetcode Solutions in Rust, Advent of Code Solutions in Rust and more

RUST GYM Rust Solutions Leetcode Solutions in Rust AdventOfCode Solutions in Rust This project demostrates how to create Data Structures and to implem

Larry Fantasy 635 Jan 3, 2023
Simple autoclicker written in Rust, to learn the Rust language.

RClicker is an autoclicker written in Rust, written to learn more about the Rust programming language. RClicker was was written by me to learn more ab

null 7 Nov 15, 2022
Rust programs written entirely in Rust

mustang Programs written entirely in Rust Mustang is a system for building programs built entirely in Rust, meaning they do not depend on any part of

Dan Gohman 561 Dec 26, 2022
Rust 核心库和标准库的源码级中文翻译,可作为 IDE 工具的智能提示 (Rust core library and standard library translation. can be used as IntelliSense for IDE tools)

Rust 标准库中文版 这是翻译 Rust 库 的地方, 相关源代码来自于 https://github.com/rust-lang/rust。 如果您不会说英语,那么拥有使用中文的文档至关重要,即使您会说英语,使用母语也仍然能让您感到愉快。Rust 标准库是高质量的,不管是新手还是老手,都可以从中

wtklbm 493 Jan 4, 2023
A library for extracting #[no_mangle] pub extern "C" functions (https://docs.rust-embedded.org/book/interoperability/rust-with-c.html#no_mangle)

A library for extracting #[no_mangle] pub extern "C" functions In order to expose a function with C binary interface for interoperability with other p

Dmitrii - Demenev 0 Feb 17, 2022
clone of grep cli written in Rust. From Chapter 12 of the Rust Programming Language book

minigrep is a clone of the grep cli in rust Minigrep will find a query string in a file. To test it out, clone the project and run cargo run body poem

Raunak Singh 1 Dec 14, 2021
Rust-blog - Educational blog posts for Rust beginners

pretzelhammer's Rust blog ?? I write educational content for Rust beginners and Rust advanced beginners. My posts are listed below in reverse chronolo

kirill 5.2k Jan 1, 2023
The ray tracer challenge in rust - Repository to follow my development of "The Raytracer Challenge" book by Jamis Buck in the language Rust

The Ray Tracer Challenge This repository contains all the code written, while step by implementing Ray Tracer, based on the book "The Ray Tracer Chall

Jakob Westhoff 54 Dec 25, 2022
Learn-rust-the-hard-way - "Learn C The Hard Way" by Zed Shaw Converted to Rust

Learn Rust The Hard Way This is an implementation of Zed Shaw's Learn X The Hard Way for the Rust Programming Language. Installing Rust TODO: Instruct

Ryan Levick 309 Dec 8, 2022