6 releases

Uses old Rust 2015

0.0.7 Oct 20, 2016
0.0.6 Oct 20, 2016

#1027 in Rust patterns

MIT license

47KB
1K SLoC

Frust Crates.io Build Status

Useful functional programming toolbelt in Rust. Still largely a WIP.

General idea is to make things easier by providing FP tools in Rust to allow for stuff like this:

use frust::monoid::*;

let v = vec![Some(1), Some(3)];
assert_eq!(combine_all(&v), Some(4));

// Slightly more magical
let t1 =       (1, 2.5f32,                String::from("hi"),  Some(3));
let t2 =       (1, 2.5f32,            String::from(" world"),     None);
let t3 =       (1, 2.5f32,         String::from(", goodbye"), Some(10));
let tuples = vec![t1, t2, t3];

let expected = (3, 7.5f32, String::from("hi world, goodbye"), Some(13));
assert_eq!(combine_all(&tuples), expected);

Table of Contents

  1. Semigroup
  2. Monoid
  3. HList
  4. Validated
  5. Todo
  6. Contributing
  7. Inspirations

Examples

Semigroup

Things that can be combined.

use frust::semigroup::*;

assert_eq!(Some(1).combine(&Some(2)), Some(3));

assert_eq!(All(3).combine(&All(5)), All(1)); // bit-wise && 
assert_eq!(All(true).combine(&All(false)), All(false));

Monoid

Things that can be combined and have an empty/id value.

use frust::monoid::*;

let t1 = (1, 2.5f32, String::from("hi"), Some(3));
let t2 = (1, 2.5f32, String::from(" world"), None);
let t3 = (1, 2.5f32, String::from(", goodbye"), Some(10));
let tuples = vec![t1, t2, t3];

let expected = (3, 7.5f32, String::from("hi world, goodbye"), Some(13));
assert_eq!(combine_all(&tuples), expected)

let product_nums = vec![Product(2), Product(3), Product(4)];
assert_eq!(combine_all(&product_nums), Product(24))

HList

Statically typed heterogeneous lists. Pop as much as you want from one of these; everything remains typed.

#[macro_use] extern crate frust;
use frust::hlist::*;

let h = hlist![true, "hello", Some(41)];
let (h1, tail1) = h.pop();
assert_eq!(h1, true);
assert_eq!(tail1, hlist!["hello", Some(41)]);

Validated

Validated is a way of running a bunch of operations that can go wrong (for example, functions returning Result<T, E>) and, in the case of one or more things going wrong, having all the errors returned to you all at once. In the case that everything went well, you get an HList of all your results.

Mapping (and otherwise working with plain) Results is different because it will stop at the first error, which can be annoying in the very common case (outlined best by the Cats project).

Here is an example of how it can be used.

#[derive(PartialEq, Eq, Debug)]
struct Person {
    age: i32,
    name: String,
}

fn get_name() -> Result<String, Error> { /* elided */ }

fn get_age() -> Result<i32, Error> { /* elided */ }

// Build up a `Validated`
let validation = get_name().into_validated() + get_age();
// When needed, turn the `Validated` back into a Result and map as usual
let try_person = validation.into_result()
                           .map(|hlist| {
                               let (name, (age, _)) = hlist.into_tuple2();
                               Person {
                                   name: name,
                                   age: age,
                               }
                           });

assert_eq!(person,
           Result::Ok(Person {
               name: "James".to_owned(),
               age: 32,
           }));

/// This next pair of functions always return Recover::Err 
fn get_name_faulty() -> Result<String, String> {
    Result::Err("crap name".to_owned())
}

fn get_age_faulty() -> Result<i32, String> {
    Result::Err("crap age".to_owned())
}

let validation2 = get_name_faulty().into_validated() + get_age_faulty();
let try_person2 = validation2.into_result()
                             .map(|_| unimplemented!());

// Notice that we have an accumulated list of errors!
assert_eq!(try_person2,
           Result::Err(vec!["crap name".to_owned(), "crap age".to_owned()]));
    

Todo

It makes sense to start by implementing things that are useful even for idiomatic Rust usage (efficient, and safe). The following might be nice to have:

  1. Validation (See cats)

These are not implemented at all, nor do I know for sure if they are possible given that Rust has no support for Higher Kinded Types. In addition, Rustaceans are used to calling iter() on collections to get a lazy view, manipulating their lists, and then doing a collect() at the end to keep things efficient. The use of these following structures maybe limited in that context.

  1. Functor
  2. Monad
  3. Apply
  4. Applicative

Show, Monoid, HList, and Semigroup are at least partially (mostly?) implemented.

Benchmarks would be nice but they're an unstable feature, so perhaps in a different branch.

Contributing

Yes please !

The following are considered important, in keeping with the spirit of Rust and functional programming:

  • Safety (type and memory)
  • Efficiency
  • Correctness

Inspirations

Scalaz, Cats, Haskell, the usual suspects ;)

No runtime deps