[][src]Crate frunk

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:

  1. HLists (heterogeneously-typed lists)
  2. LabelledGeneric, and Generic
  3. Coproduct
  4. Validated (accumulator for Result)
  5. Semigroup
  6. 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");Run
Transmogrifying

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);Run

Links:

  1. Source on Github
  2. Crates.io page

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::ToMut;
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

coproduct

Module that holds Coproduct data structures, traits, and implementations

generic

This module holds the machinery behind Generic.

hlist

Module that holds HList data structures, implementations, and typeclasses.

indices

Types used for indexing into HLists and coproducts.

labelled

This module holds the machinery behind LabelledGeneric.

monoid

Module for holding Monoid typeclass definitions and default implementations

path

Holds models, traits, and logic for generic traversal of models

prelude

Traits that need to be imported for the complete frunk experience.

semigroup

Module for holding the Semigroup typeclass definition and typeclass instances

traits

Traits that provide generic functionality for multiple types in frunk

validated

Module for holding Validated logic

Macros

Coprod

Returns a type signature for a Coproduct of the provided types

Hlist

Returns a type signature for an HList of the provided types

field

Used for creating a Field

hlist

Returns an HList based on the values passed in.

hlist_pat

Macro for pattern-matching on HLists.

poly_fn

Returns a polymorphic function for use with mapping/folding heterogeneous types.