Expand description
Frunk: generic functional programming toolbelt for Rust
Aims to be a collection of functional programming abstractions implemented in Rust in effective, useful, and idiomatic ways. Examples of things that are included in rust are:
- HLists (heterogeneously-typed lists)
- LabelledGeneric, and Generic
- Coproduct
- Validated (accumulator for Result)
- Semigroup
- Monoid
Here is a small taste of what Frunk has to offer:
use frunk::prelude::*;
use frunk::{self, monoid, Semigroup, Generic};
// Combining Monoids
let v = vec![Some(1), Some(3)];
assert_eq!(monoid::combine_all(&v), Some(4));
// HLists
let h = hlist![1, "hi"];
assert_eq!(h.len(), 2);
let hlist_pat!(a, b) = h;
assert_eq!(a, 1);
assert_eq!(b, "hi");
let h1 = hlist![Some(1), 3.3, 53i64, "hello".to_owned()];
let h2 = hlist![Some(2), 1.2, 1i64, " world".to_owned()];
let h3 = hlist![Some(3), 4.5, 54, "hello world".to_owned()];
assert_eq!(h1.combine(&h2), h3);
// Generic and LabelledGeneric-based programming
// Allows Structs to play well easily with HLists
#[derive(Generic, LabelledGeneric)]
struct ApiUser<'a> {
FirstName: &'a str,
LastName: &'a str,
Age: usize,
}
#[derive(Generic, LabelledGeneric)]
struct NewUser<'a> {
first_name: &'a str,
last_name: &'a str,
age: usize,
}
#[derive(LabelledGeneric)]
struct SavedUser<'a> {
first_name: &'a str,
last_name: &'a str,
age: usize,
}
// Instantiate a struct from an HList. Note that you can go the other way too.
let a_user: ApiUser = frunk::from_generic(hlist!["Joe", "Blow", 30]);
// Convert using Generic
let n_user: NewUser = Generic::convert_from(a_user); // done
// Convert using LabelledGeneric
//
// This will fail if the fields of the types converted to and from do not
// have the same names or do not line up properly :)
//
// Also note that we're using a helper method to avoid having to use universal
// function call syntax
let s_user: SavedUser = frunk::labelled_convert_from(n_user);
assert_eq!(s_user.first_name, "Joe");
assert_eq!(s_user.last_name, "Blow");
assert_eq!(s_user.age, 30);
// Uh-oh ! last_name and first_name have been flipped!
#[derive(LabelledGeneric)]
struct DeletedUser<'a> {
last_name: &'a str,
first_name: &'a str,
age: usize,
}
// let d_user = <DeletedUser as LabelledGeneric>::convert_from(s_user); <-- this would fail at compile time :)
// This will, however, work, because we make use of the Sculptor type-class
// to type-safely reshape the representations to align/match each other.
let d_user: DeletedUser = frunk::transform_from(s_user);
assert_eq!(d_user.first_name, "Joe");
RunTransmogrifying
Sometimes you need might have one data type that is “similar in shape” to another data type, but it
is similar recursively (e.g. it has fields that are structs that have fields that are a superset of
the fields in the target type, so they are transformable recursively). .transform_from
can’t
help you there because it doesn’t deal with recursion, but the Transmogrifier
can help if both
are LabelledGeneric
by transmogrify()
ing from one to the other.
What is “transmogrifying”? In this context, it means to recursively transform some data of type A into data of type B, in a typesafe way, as long as A and B are “similarly-shaped”. In other words, as long as B’s fields and their subfields are subsets of A’s fields and their respective subfields, then A can be turned into B.
As usual, the goal with Frunk is to do this:
- Using stable (so no specialisation, which would have been helpful, methinks)
- Typesafe
- No usage of
unsafe
Here is an example:
use frunk::labelled::Transmogrifier;
#[derive(LabelledGeneric)]
struct InternalPhoneNumber {
emergency: Option<usize>,
main: usize,
secondary: Option<usize>,
}
#[derive(LabelledGeneric)]
struct InternalAddress<'a> {
is_whitelisted: bool,
name: &'a str,
phone: InternalPhoneNumber,
}
#[derive(LabelledGeneric)]
struct InternalUser<'a> {
name: &'a str,
age: usize,
address: InternalAddress<'a>,
is_banned: bool,
}
#[derive(LabelledGeneric, PartialEq, Debug)]
struct ExternalPhoneNumber {
main: usize,
}
#[derive(LabelledGeneric, PartialEq, Debug)]
struct ExternalAddress<'a> {
name: &'a str,
phone: ExternalPhoneNumber,
}
#[derive(LabelledGeneric, PartialEq, Debug)]
struct ExternalUser<'a> {
age: usize,
address: ExternalAddress<'a>,
name: &'a str,
}
let internal_user = InternalUser {
name: "John",
age: 10,
address: InternalAddress {
is_whitelisted: true,
name: "somewhere out there",
phone: InternalPhoneNumber {
main: 1234,
secondary: None,
emergency: Some(5678),
},
},
is_banned: true,
};
/// Boilerplate-free conversion of a top-level InternalUser into an
/// ExternalUser, taking care of subfield conversions as well.
let external_user: ExternalUser = internal_user.transmogrify();
let expected_external_user = ExternalUser {
name: "John",
age: 10,
address: ExternalAddress {
name: "somewhere out there",
phone: ExternalPhoneNumber {
main: 1234,
},
}
};
assert_eq!(external_user, expected_external_user);
RunLinks:
Re-exports
pub use hlist::lift_from;
pub use hlist::HCons;
pub use hlist::HNil;
pub use traits::Func;
pub use traits::Poly;
pub use traits::ToRef;
pub use coproduct::Coproduct;
pub use generic::convert_from;
pub use generic::from_generic;
pub use generic::into_generic;
pub use generic::map_inter;
pub use generic::map_repr;
pub use generic::Generic;
pub use labelled::from_labelled_generic;
pub use labelled::into_labelled_generic;
pub use labelled::labelled_convert_from;
pub use labelled::transform_from;
pub use labelled::LabelledGeneric;
pub use semigroup::Semigroup;
pub use monoid::Monoid;
pub use validated::Validated;
Modules
Generic
.frunk
experience.Macros
HList
based on the values passed in.