Git Product home page Git Product logo

frunk's Introduction

Frunk Crates.io Continuous integration Gitter Frunk

frunk frəNGk

  • Functional programming toolbelt in Rust.
  • Might seem funky at first, but you'll like it.
  • Comes from: funktional (German) + Rust → Frunk

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

use frunk::monoid::combine_all;

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

For a deep dive, RustDocs are available for:

Table of Contents

  1. HList
  2. Generic
  3. Coproduct
  4. Validated
  5. Semigroup
  6. Monoid
  7. Features
  8. Benchmarks
  9. Todo
  10. Contributing
  11. Inspirations
  12. Maintainers

Examples

HList

Statically typed heterogeneous lists.

First, let's enable hlist:

use frunk::{HNil, HCons, hlist};

Some basics:

let h = hlist![1];
// Type annotations for HList are optional. Here we let the compiler infer it for us
// h has a static type of: HCons<i32, HNil>

// HLists have a head and tail
assert_eq!(hlist![1].head, 1);
assert_eq!(hlist![1].tail, HNil);

// You can convert a tuple to an HList and vice-versa
let h2 = hlist![ 42f32, true, "hello" ];
let t: (f32, bool, &str) = h2.into();
assert_eq!(t, (42f32, true, "hello"));

let t3 = (999, false, "world");
let h3: HList![ isize, bool, &str ] = t3.into();
assert_eq!(h3, hlist![ 999, false, "world" ]);

HLists have a hlist_pat! macro for pattern matching;

let h: HList!(&str, &str, i32, bool) = hlist!["Joe", "Blow", 30, true];
// We use the HList! type macro to make it easier to write
// a type signature for HLists, which is a series of nested HCons
// h has an expanded static type of: HCons<&str, HCons<&str, HCons<i32, HCons<bool, HNil>>>>

let hlist_pat!(f_name, l_name, age, is_admin) = h;
assert_eq!(f_name, "Joe");
assert_eq!(l_name, "Blow");
assert_eq!(age, 30);
assert_eq!(is_admin, true);

// You can also use into_tuple2() to turn the hlist into a nested pair

To traverse or build lists, you can also prepend/or pop elements at the front:

let list = hlist![true, "hello", Some(41)];
// h has a static type of: HCons<bool, HCons<&str, HCons<Option<{integer}>, HNil>>>
let (head1, tail1) = list.pop();
assert_eq!(head1, true);
assert_eq!(tail1, hlist!["hello", Some(41)]);
let list1 = tail1.prepend(head1);
assert_eq!(list, list1);

// or using macro sugar:
let hlist_pat![head2, ...tail2] = list; // equivalent to pop
let list2 = hlist![head2, ...tail2];    // equivalent to prepend
assert_eq!(list, list2);

You can reverse, map, and fold over them too:

// Reverse
let h1 = hlist![true, "hi"];
assert_eq!(h1.into_reverse(), hlist!["hi", true]);

// Fold (foldl and foldr exist)
let h2 = hlist![1, false, 42f32];
let folded = h2.foldr(
    hlist![
        |acc, i| i + acc,
        |acc, _| if acc > 42f32 { 9000 } else { 0 },
        |acc, f| f + acc
    ],
    1f32
);
assert_eq!(folded, 9001)

// Map
let h3 = hlist![9000, "joe", 41f32];
let mapped = h3.map(hlist![
    |n| n + 1,
    |s| s,
    |f| f + 1f32]);
assert_eq!(mapped, hlist![9001, "joe", 42f32]);

You can pluck a type out of an HList using pluck(), which also gives you back the remainder after plucking that type out. This method is checked at compile-time to make sure that the type you ask for can be extracted.

let h = hlist![1, "hello", true, 42f32];
let (t, remainder): (bool, _) = h.pluck();
assert!(t);
assert_eq!(remainder, hlist![1, "hello", 42f32])

Similarly, you can re-shape, or sculpt, an Hlist, there is a sculpt() method, which allows you to re-organise and/or cull the elements by type. Like pluck(), sculpt() gives you back your target with the remainder data in a pair. This method is also checked at compile time to make sure that it won't fail at runtime (the types in your requested target shape must be a subset of the types in the original HList.

let h = hlist![9000, "joe", 41f32, true];
let (reshaped, remainder): (HList![f32, i32, &str], _) = h.sculpt();
assert_eq!(reshaped, hlist![41f32, 9000, "joe"]);
assert_eq!(remainder, hlist![true]);

Generic

Generic is a way of representing a type in ... a generic way. By coding around Generic, you can to write functions that abstract over types and arity, but still have the ability to recover your original type afterwards. This can be a fairly powerful thing.

Setup

In order to derive the trait Generic (or LabelledGeneric) you will have to add frunk_core dependency

[dependencies]
frunk_core = { version = "$version" }

Frunk comes out of the box with a nice custom Generic derivation so that boilerplate is kept to a minimum.

Here are some examples:

HList ⇄ Struct

#[derive(Generic, Debug, PartialEq)]
struct Person<'a> {
    first_name: &'a str,
    last_name: &'a str,
    age: usize,
}

let h = hlist!("Joe", "Blow", 30);
let p: Person = frunk::from_generic(h);
assert_eq!(p,
           Person {
               first_name: "Joe",
               last_name: "Blow",
               age: 30,
           });

This also works the other way too; just pass a struct to into_generic and get its generic representation.

Converting between Structs

Sometimes you may have 2 different types that are structurally the same (e.g. different domains but the same data). Use cases include:

  • You have a models for deserialising from an external API and equivalents for your app logic
  • You want to represent different stages of the same data using types (see this question on StackOverflow)

Generic comes with a handy convert_from method that helps make this painless:

// Assume we have all the imports needed
#[derive(Generic)]
struct ApiPerson<'a> {
    FirstName: &'a str,
    LastName: &'a str,
    Age: usize,
}

#[derive(Generic)]
struct DomainPerson<'a> {
    first_name: &'a str,
    last_name: &'a str,
    age: usize,
}

let a_person = ApiPerson {
                   FirstName: "Joe",
                   LastName: "Blow",
                   Age: 30,
};
let d_person: DomainPerson = frunk::convert_from(a_person); // done

LabelledGeneric

In addition to Generic, there is also LabelledGeneric, which, as the name implies, relies on a generic representation that is labelled. This means that if two structs derive LabelledGeneric, you can convert between them only if their field names match!

Here's an example:

// Suppose that again, we have different User types representing the same data
// in different stages in our application logic.

#[derive(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,
}

let n_user = NewUser {
    first_name: "Joe",
    last_name: "Blow",
    age: 30
};

// Convert from a NewUser to a Saved 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,
}

//  This would fail at compile time :)
let d_user: DeletedUser = frunk::labelled_convert_from(s_user);

// 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);
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 tranform 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);

Note that as of writing, there are a couple of known limitations with transmogrify(), some of which may be addressed in the future:

  • If one of the fields is an identical type and derives LabelledGeneric, the compiler will tell you that it can't "infer an index" for transmogrify(); this is because impls of the Transmogrifier trait will clash. This may or may not change in the future (perhaps if we move to a pure procedural macro powered way of doing things?)
  • For types that contain many multiple deeply-nested fields that require transmogfiy()ing, using this technique will likely increase your compile time.
  • If you've balked at the the compile-time errors with transform_from when a transform is deemed impossible (e.g. missing field), the errors for transmogrify() are worse to the degree that recursive transmogrify() is required for your types.

For more information how Generic and Field work, check out their respective Rustdocs:

Path

One of the other things that LabelledGeneric-deriving structs can do is be generically traversed using Path and its companion trait PathTraverser. In some circles, this functionality is also called a Lens.

Path-based traversals are

  • Easy to use through the procedural macro path! (frunk_proc_macros)
    • Traversing multiple levels is familiar; just use dot . syntax (path!(nested.attribute.value))
  • Compile-time safe
  • Composable (add one to the other using +)
  • Allows you to get by value, get by reference or get by mutable reference, depending on the type of thing you pass it.
#[derive(LabelledGeneric)]
struct Dog<'a> {
    name: &'a str,
    dimensions: Dimensions,
}

#[derive(LabelledGeneric)]
struct Cat<'a> {
    name: &'a str,
    dimensions: Dimensions,
}

#[derive(LabelledGeneric)]
struct Dimensions {
    height: usize,
    width: usize,
    unit: SizeUnit,
}

#[derive(Debug, Eq, PartialEq)]
enum SizeUnit {
    Cm,
    Inch,
}

let mut dog = Dog {
    name: "Joe",
    dimensions: Dimensions {
        height: 10,
        width: 5,
        unit: SizeUnit::Inch,
    },
};

let cat = Cat {
    name: "Schmoe",
    dimensions: Dimensions {
        height: 7,
        width: 3,
        unit: SizeUnit::Cm,
    },
};

// generic, re-usable, compsable paths
let dimensions_lens = path!(dimensions);
let height_lens = dimensions_lens + path!(height); // compose multiple
let unit_lens = path!(dimensions.unit); // dot syntax to just do the whole thing at once

assert_eq!(*height_lens.get(&dog), 10);
assert_eq!(*height_lens.get(&cat), 7);
assert_eq!(*unit_lens.get(&dog), SizeUnit::Inch);
assert_eq!(*unit_lens.get(&cat), SizeUnit::Cm);

// modify by passing a &mut
*height_lens.get(&mut dog) = 13;
assert_eq!(*height_lens.get(&dog), 13);

There's also a Path! type-level macro for declaring shape-constraints. This allows you to write adhoc shape-dependent functions for LabelledGeneric types.

// Prints height as long as `A` has the right "shape" (e.g.
// has `dimensions.height: usize` and `dimension.unit: SizeUnit)
fn print_height<'a, A, HeightIdx, UnitIdx>(obj: &'a A) -> ()
where
    &'a A: PathTraverser<Path!(dimensions.height), HeightIdx, TargetValue = &'a usize>
        + PathTraverser<Path!(dimensions.unit), UnitIdx, TargetValue = &'a SizeUnit>,
{
    println!(
        "Height [{} {:?}]",
        path!(dimensions.height).get(obj),
        path!(dimensions.unit).get(obj)
    );
}

See examples/paths.rs to see how this works.

Coproduct

If you've ever wanted to have an adhoc union / sum type of types that you do not control, you may want to take a look at Coproduct. In Rust, thanks to enum, you could potentially declare one every time you want a sum type to do this, but there is a light-weight way of doing it through Frunk:

use frunk::prelude::*; // for the fold method

// Declare the types we want in our Coproduct
type I32F32Bool = Coprod!(i32, f32, bool);

let co1 = I32F32Bool::inject(3);
let get_from_1a: Option<&i32> = co1.get();
let get_from_1b: Option<&bool> = co1.get();

assert_eq!(get_from_1a, Some(&3));
// None because co1 does not contain a bool, it contains an i32
assert_eq!(get_from_1b, None);

// This will fail at compile time because i8 is not in our Coproduct type
let nope_get_from_1b: Option<&i8> = co1.get(); // <-- will fail
// It's also impossible to inject something into a coproduct that is of the wrong type
// (not contained in the coproduct type)
let nope_co = I32F32Bool::inject(42f64); // <-- will fail

// We can fold our Coproduct into a single value by handling all types in it
assert_eq!(
    co1.fold(hlist![|i| format!("int {}", i),
                    |f| format!("float {}", f),
                    |b| (if b { "t" } else { "f" }).to_string()]),
    "int 3".to_string());

For more information, check out the docs for Coproduct

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).

To use Validated, first:

use frunk::prelude::*; // for Result::into_validated

Assuming we have a Person struct defined

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

Here is an example of how it can be used in the case that everything goes smoothly.

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

// Build up a `Validated` by adding in any number of `Result`s
let validation = get_name().into_validated() + get_age() + get_street();
// When needed, turn the `Validated` back into a Result and map as usual
let try_person = validation.into_result()
                           // Destructure our hlist
                           .map(|hlist_pat!(name, age, street)| {
                               Person {
                                   name: name,
                                   age: age,
                                   street: street,
                               }
                           });

assert_eq!(try_person.unwrap(),
           Person {
               name: "James".to_owned(),
               age: 32,
               street: "Main".to_owned(),
           }));
}

If, on the other hand, our Results are faulty:

/// 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.unwrap_err(),
           vec!["crap name".to_owned(), "crap age".to_owned()]);

Semigroup

Things that can be combined.

use frunk::Semigroup;
use frunk::semigroup::All;

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 frunk::monoid::combine_all;

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

Features

Frunk comes with support for deriving serde serializer/deserializers for its core data structures. This can be enabled by adding the serde feature flag.

For example, if you'd like to use just frunk_core with serde

[dependencies]
frunk_core = { version = "$version", features = ["serde"] }

Or, if you'd like to use frunk with serde, you need to explicitly include frunk_core as well

[dependencies]
frunk = { version = "$version", features = ["serde"] }
frunk_core = { version = "$version", features = ["serde"] }

Benchmarks

Benchmarks are available in ./benches and can be run with:

$ rustup run nightly cargo bench

Benchmarks on master are also auto-generated, uploaded and available online.

Todo

Stabilise interface, general cleanup

Before a 1.0 release, would be best to revisit the design of the interfaces and do some general code (and test cleanup).

Not yet implemented

Given that Rust has no support for Higher Kinded Types, I'm not sure if these are even possible to implement. In addition, Rustaceans are used to calling iter() on collections to get a lazy view, manipulating their elements with map or and_then, and then doing a collect() at the end to keep things efficient. The usefulness of these following structures maybe limited in that context.

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

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, Shapeless, Cats, Haskell, the usual suspects ;)

Maintainers

A.k.a. people whom you can bug/tag/@ on Gitter :D

  1. lloydmeta
  2. Centril
  3. ExpHP

frunk's People

Contributors

agluszak avatar bgr360 avatar bossmc avatar centril avatar cobaltcause avatar dependabot[bot] avatar diggsey avatar dima-starosud avatar exphp avatar figsoda avatar fusillicode avatar immemorconsultrixcontrarie avatar jesskfullwood avatar jswrenn avatar kichjang avatar kpp avatar lloydmeta avatar mbrobbel avatar mglagla avatar mwillsey avatar oriontvv avatar remexre avatar rosefromthedead avatar skade avatar steffahn avatar tupshin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

frunk's Issues

Benchmarks

Concerning this: https://github.com/lloydmeta/frunk#benchmarks

You can use cargo support for benchmarks to circumvent this solution.

  1. Create a folder called benches/
  2. Put your benchmarks inside as files
  3. Run them with cargo bench. Which obviously only works on nightly, but, well, just call it using nightly toolchains then :).

Should we rename Coproduct to Sum?

It occurs to me that Sum is both shorter (so better ergonomics) and probably more commonly in use than Coprod since the latter is more familiar to a category theorist than someone who knows about algebraic data types and has heard about sum types...

The downside is some breakage; but we could do that gradually by introducing it as an alias, deprecate Coproduct and eventually remove it.

What do you think?

Rest pattern in hlist/coprod macros

I think it can be useful to have a syntax for representing "the rest of the list" in hlist!, Coprod!, etc

It is tempting to use ..rest for this syntax but that would be ambiguous with otherwise perfectly valid patterns and expressions. I suggest /rest as the syntax:

// Trait for HLists with at least two elements
pub trait AtLeastTwo { }

impl<A, B, Rest> AtLeastTwo for Hlist!(A, B, /Rest) { }
let hlist_pat![head, /tail] = list;

assert_eq!(list, hlist![head, /tail]);

I can basically just copy over what I did here if there is interest.

Moving `Coproduct` (and associated traits) to `frunk_core`

Having Coproduct in frunk_core could let you derive enums in the future lets dependencies of frunk_core get access to Coproduct without having to pay for a dependency on syn in frunk_derive... I think this makes frunk_core more appetizing as a dependency. What do you think?

Make Monoid and Semigroup more general?

The current declaration of Semigroup takes its (value-level) parameters by reference. Taking them by value would be strictly more general and—in many cases—much more efficient, and I would be willing to undertake the work necessary to alter the library's code accordingly, but I wanted to check first whether this was desirable. If I get the go-ahead, I'll put together a pull request with the necessary changes.

Create a nursery project?

It might be nice with the next minor version to start a "nursery" project to hold "cutting-edge" or trial concepts.

We could put things like Transmogrifier and PathTraverser in there, basically stuff that hold promise, but have a few known edge cases or are waiting for more compiler support to become really smooth experiences.

Note, I don't think this module should be used for holding things that require nightly ...

Macro to call `h_cons()` repeatedly

It seems like this should be pretty simple to implement, just something like this should do:

macro_rules! h_cons (
    ($hlist:expr; $($cons:expr),+) => {
        {
            let hlist = $hlist;
            $(let hlist = h_cons($cons, hlist);)+
            hlist
         }
    }
);

Incorrect use of `deprecated` attribute

rust-lang/rust#56896 is making the correct use of deprecated mandatory in rustc. We can't apply this as a hard error due to the following use in frunk_core:

[INFO] [stderr]    Compiling frunk_core v0.2.2
[INFO] [stderr] error: expected meta item sequence
[INFO] [stderr]    --> /opt/crater/cargo-home/registry/src/github.com-1ecc6299db9ec823/frunk_core-0.2.2/src/labelled.rs:223:5
[INFO] [stderr]     |
[INFO] [stderr] 223 |     #[deprecated = "obsolete, transform_from instead"]
[INFO] [stderr]     |     ^^-----------------------------------------------^
[INFO] [stderr]     |       |
[INFO] [stderr]     |       help: use the `note` key: `deprecated(note = "obsolete, transform_from instead")`

Documenting rules for when type inference can solve for indices

I do a small bit of this in the coproduct embed PR, but I think there should be more:

There should be one central piece of documentation (which other methods can link to) describing the Index technique commonly used in frunk, describing how:

  • Users should generally not worry about Index type parameters. Even when using turbofish syntax (once available), these should generally be left to type inference using _.
  • Generally speaking, type inference can solve for the indices whenever there is a unique solution.
  • This means that methods like hlist's pluck and coproduct's inject succeed when the type appears exactly once in the HList/coproduct.

It should also explain workarounds for when type inference fails:

  • The preferred method, whenever possible, should be to use newtypes in your HList/coproduct, so that all of the types are distinct. (@Centril, @lloydmeta do you agree?)
  • As a last resort, indices can be specified explicitly, but I'm not sure that the docs should be suggesting this course of action quite yet as they feel mostly like an implementation detail. (maybe once reified indices are added, if I ever get to that; I still need to get more experience with them first in funky)

`labelled_convert_from` does not compile

#[macro_use]
extern crate frunk;
extern crate frunk_core;

use frunk::labelled::*;

#[derive(LabelledGeneric, Debug)]
struct User1<'a> {
    first_name: &'a str,
    last_name: &'a str,
    age: u16
}

#[derive(LabelledGeneric, Debug)]
struct User2<'a> {
    last_name: &'a str,
    first_name: &'a str,
    age: u16
}

fn main() {
    let user1 = User1 {
        first_name: "first",
        last_name: "last",
        age: 34,
    };

    let user2: User2 = labelled_convert_from(user1);

    println!("{:?}", user2);
}

Does not compile:

cargo.exe build --all
   Compiling frunk_test v0.1.0 (file:///E:/github/frunk_test)
error[E0271]: type mismatch resolving `<User2<'_> as frunk_core::labelled::LabelledGeneric>::Repr == frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::f, frunk_core::labelled::i, frunk_core::labelled::r, frunk_core::labelled::s, frunk_core::labelled::t, frunk_core::labelled::__, frunk_core::labelled::n, frunk_core::labelled::a, frunk_core::labelled::m, frunk_core::labelled::e), &str>, frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::l, frunk_core::labelled::a, frunk_core::labelled::s, frunk_core::labelled::t, frunk_core::labelled::__, frunk_core::labelled::n, frunk_core::labelled::a, frunk_core::labelled::m, frunk_core::labelled::e), &str>, frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::a, frunk_core::labelled::g, frunk_core::labelled::e), u16>, frunk_core::hlist::HNil>>>`
  --> src\main.rs:28:24
   |
28 |     let user2: User2 = labelled_convert_from(user1);
   |                        ^^^^^^^^^^^^^^^^^^^^^ expected a tuple with 9 elements, found one with 10 elements
   |
   = note: expected type `frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::l, frunk_core::labelled::a, frunk_core::labelled::s, frunk_core::labelled::t, frunk_core::labelled::__, frunk_core::labelled::n, frunk_core::labelled::a, frunk_core::labelled::m, frunk_core::labelled::e), &str>, frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::f, frunk_core::labelled::i, frunk_core::labelled::r, frunk_core::labelled::s, frunk_core::labelled::t, frunk_core::labelled::__, frunk_core::labelled::n, frunk_core::labelled::a, frunk_core::labelled::m, frunk_core::labelled::e), &str>, _>>`
              found type `frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::f, frunk_core::labelled::i, frunk_core::labelled::r, frunk_core::labelled::s, frunk_core::labelled::t, frunk_core::labelled::__, frunk_core::labelled::n, frunk_core::labelled::a, frunk_core::labelled::m, frunk_core::labelled::e), &str>, frunk_core::hlist::HCons<frunk_core::labelled::Field<(frunk_core::labelled::l, frunk_core::labelled::a, frunk_core::labelled::s, frunk_core::labelled::t, frunk_core::labelled::__, frunk_core::labelled::n, frunk_core::labelled::a, frunk_core::labelled::m, frunk_core::labelled::e), &str>, _>>`
   = note: required by `frunk_core::labelled::labelled_convert_from`

error: aborting due to previous error(s)

error: Could not compile `frunk_test`.

To learn more, run the command again with --verbose.

Process finished with exit code 101

Transmogrification fails if common field implements `LabelledGeneric`

A basic (working) example of transmogrification might look like:

use frunk_core::labelled::Transmogrifier;

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Foo {
    pub bar: Bar,
}

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Foo2 {
    pub bar: Bar2,
}

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Bar {
    pub index: i64,
}

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Bar2 {
    pub index: i64,
}

fn main() {
    let foo = Foo {
        bar: Bar { index: 4 }
    };
    let foo: Foo2 = foo.transmogrify();
    println!("{:?}", foo);
}

This compiles and works just fine but, if you change the definition of Foo2 to be[1]:

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Foo2 {
    pub bar: Bar,
}

Then the compile fails with the error:

error[E0282]: type annotations needed
  --> src/main.rs:27:25
   |
27 |     let foo: Foo2 = foo.transmogrify();
   |                         ^^^^^^^^^^^^ cannot infer type for `TransMogSourceHeadValueIndices`

Strangely, if Bar does not implement LabelledGeneric then the code compiles and works as expected. (Have you accidentally tricked the compiler into implementing specialization on stable?!)

This makes it hard to mix types in structs, since no two LabelledGeneric fields can ever be common between two LabelledGeneric structs.

[1](Note that this means that Foo and Foo2 are identical, though this need not be the case in general, as other fields may be present and differ up to transmogrifiability without affecting the result)

Coproducts and generics + trait bounds?

I'm working with some code that filters specific coproducts out of an mpsc and running into problems. (Originally this was a huge enum of disjoint structs and I'm trying to rewrite it using frunk because I'm tired of not having anonymous enum variants.) I've distilled this down to an example that I feel should compile, but Rust is obviously unhappy that T could be a type that doesn't belong to the Coproduct and I'm getting trait bound errors. Is there a trait bound I can add to T to make this work, or should this be solved by CNil implementing the trait and always returning None?

extern crate frunk_core;

pub type SimpleCoprod = Coprod!(
    i32,
    String,
);

/// Take and return Option
pub fn foo<T>(f: SimpleCoprod) -> Option<T> {
    f.take()
}
error[E0277]: the trait bound `frunk_core::coproduct::CNil: frunk_core::coproduct::CoproductTaker<T, _>` is not satisfied
  --> src/noworky.rs:10:7
   |
10 |     f.take()
   |       ^^^^ the trait `frunk_core::coproduct::CoproductTaker<T, _>` is not implemented for `frunk_core::coproduct::CNil`
   |
   = note: required because of the requirements on the impl of `frunk_core::coproduct::CoproductTaker<T, frunk_core::indices::There<_>>` for `frunk_core::coproduct::Coproduct<std::string::String, frunk_core::coproduct::CNil>`
   = note: required because of the requirements on the impl of `frunk_core::coproduct::CoproductTaker<T, frunk_core::indices::There<frunk_core::indices::There<_>>>` for `frunk_core::coproduct::Coproduct<i32, frunk_core::coproduct::Coproduct<std::string::String, frunk_core::coproduct::CNil>>`

Iterator extension traits for folding monoids/semigroups

There should be something to fold an iterator of monoids down to a single value, or to create the Monoids first and then fold them. fold_map is one of my favorite things in haskell.

Vague ideas: (not thinking too hard about references and whatnot, since I'd rather see #57 before this)

vec![Product(1), Product(2)].iter().combine();

xs.iter().fold_map(Product);                  // -> Product(i32)   (for Monoids)
xs.iter().fold1_map(Product);                 // -> Option<Product(i32)> (for Semigroups)
xs.iter().fold_map_onto(Product(1), Product); // -> Product(i32)         (for Semigroups)

or maybe we can do better, with the understanding that fold_map is really only useful for newtypes (so it might as well get unwrapped at the end; this will require some sort of Newtype or Iso(-morphism) trait)

// where Product<i32> implements some trait that provides
//      conversions for i32 <-> Product<i32>
vec![1, 2].iter().combine_like(Product);         // -> i32   (for Monoids)
vec![1, 2].iter().combine_option_like(Product);  // -> Option<i32> (for Semigroups)
vec![1, 2].iter().combine_onto_like(1, Product); // -> i32         (for Semigroups)

Mapping coproducts

I discovered there was no map for coproducts when writing the docs for CoprodEmbedder, where I mention what happens if there are duplicate types in the input (which is something that typically only ever really happens as the result of mapping, so I couldn't come up with a palatable code example).

map for coproducts should probably have all of the same features as the one on HLists:

  • Mapping with an hlist![] of FnOnces or a single FnOnce
  • Support for mapping by reference.
  • Anything else I missed

I think I'll get to this at some point after implementing the ergonomics hacks in #90.

make a subset of Frunk 'no_std' compatible.

Heya!
I work a lot on embedded no_std projects, and i think having Frunk (or rather, a subset) be no_std compatible would be very nice.
I looked through the code, and everything using std::cmp, std::ops, marker types, std::cell, etc is available under core as well. The main culprit are the impls for monoid and semigroup, because the collections crate is usually not available. However, there are alternatives (for example, for vec and hashmaps: https://japaric.github.io/heapless/heapless/index.html).
I Dont think it would be that hard to split it off in a nice way as to allow impls of monoid and friends to be under a cfg flag, for example.
I'd like to help if there is some interest, would just like to know if there is a massive use case for std that i totally missed :)

Getting rid of `CNil`

This is a follow up issue to #93.

I think we should get rid of special uninhabited types unless we have a particularly good reason to keep them. The standard library is in the process of getting rid of Infallible and other uninhabited types.

So I think we should replace CNil and Here with just !.

Thoughts?

Example doesn't work unless frink_core added as an explicit dependency

extern crate frunk;
#[macro_use] // for the hlist macro
extern crate frunk_core;
use frunk::*; // for the Generic trait and HList

#[derive(Generic, Debug, PartialEq)]
struct Person<'a> {
    first_name: &'a str,
    last_name: &'a str,
    age: usize,
}

let h = hlist!("Joe", "Blow", 30);
let p: Person = from_generic(h);
assert_eq!(p,
           Person {
               first_name: "Joe",
               last_name: "Blow",
               age: 30,
           });

This example given in the README does not seem to work.
This does:

#[macro_use]
extern crate frunk;
extern crate frunk_core;
use frunk_core::generic::*;

#[derive(Generic, Debug, PartialEq)]
struct Person<'a> {
    first_name: &'a str,
    last_name: &'a str,
    age: usize,
}

fn main() {
    let h = hlist!("Joe", "Blow", 30);
    let p: Person = from_generic(h);
    assert_eq!(p,
               Person {
                   first_name: "Joe",
                   last_name: "Blow",
                   age: 30,
               });
    println!("success!");
}

With my Cargo.toml:

[dependencies]
frunk = "0.1.29"
frunk_core = "0.0.17"

Please let me know if I am missing something, I am very new to Rust.
Thank you for building such a wonderful crate!

HList! vs Hlist!

Since we're talking breaking changes, I'd like to throw this out there:

I mistype Hlist! as HList! almost every time I use it. Why is it named this way, and how do others feel about this?

Add a &self context to Func

If #137 gets merged, then Func will take no context while FuncMut takes a &mut context. For consistency, Func should be revised to take a &self context.

IMO, poly_fn! still should remain limited to defining Funcs with unit context, because it is too complicated to define an ergonomical grammar for a macro that allows the function to capture anyhing. In other words, to take advantage of this new feature will require a manual Func impl.

hlist as a type to be declared

struct Row<F> {
    fields: HCons<F, HNil>,
    tags: HashSet<MetaTag>
}

I have declared an HList like this - is this right notation? I am rust newbie and I am attempting to translate some of the things i love in scala here. Note that field (F in this case) is my own impl for a trait that may be type bound later - with a generic type parameter T - row attempts to start with head field as a seed field. Due to lack of documentation I wanted to ask this here ... please let me know if you already have documentation describing this.

Meta-issue for ergonomic advancements

Followup to #90. Here's the grander scheme as I see it:

  • Add inherent methods
    • To HLists and Coproducts (done in #99)
    • Anything else? (checked while doing #103)
    • For HMappable and friends now that the impls for &Type are gone
  • Reexport the most important things at the root (done in #103)
    • HCons, HNil, Coproduct, HList
    • What else from the other modules?
  • Introduce a prelude module for traits that need to be imported to be used (done in #103)
    • extension traits for types outside frunk
    • things like HMappable that have impls for &HCons
      • Or we can take this opportunity to introduce a more general Mappable trait
  • [#] (maybe!) Rename the traits in line with the rust convention of Verb::verb
    • I don't think this is worth it right now
  • Take advantage of the new features in docs and examples
    • use turbofish!
    • no more use frunk::hlist::*
    • in README.md
    • in examples
      • if necessary, add frunk as dev dependency to frunk-core so doc examples can use frunk's prelude (or add a prelude to frunk-core, but I think having two makes it harder to maintain)

HList maps and folds may not need an Index parameter

Here's the result of my investigation so far:

Simply removing the Index param from HMappable results in conflicting impls, but these can be resolved by changing the impls for borrowed hlists a bit:

// old (requires Index param to resolve ambiguities)
impl HMappable<F>                    for HNil { ... }
impl HMappable<F>                    for HCons<H, Tail> { ... }
impl HMappable<HCons<F, MapperTail>> for HCons<H, Tail> { ... }
impl HMappable<F>                    for &'a HCons<H, HNil> { ... } <-- removed
impl HMappable<HCons<F, HNil>>       for &'a HCons<H, HNil> { ... } <-- removed
impl HMappable<F>                    for &'a HCons<H, Tail> { ... }
impl HMappable<HCons<F, MapperTail>> for &'a HCons<H, Tail> { ... }

// new (unambiguous)
impl HMappable<F>                    for HNil { ... }
impl HMappable<F>                    for HCons<H, Tail> { ... }
impl HMappable<HCons<F, MapperTail>> for HCons<H, Tail> { ... }
impl HMappable<F>                    for &'a HNil { ... }           <-- added
impl HMappable<F>                    for &'a HCons<H, Tail> { ... }
impl HMappable<HCons<F, MapperTail>> for &'a HCons<H, Tail> { ... }

Looking around, I see the other traits also don't have impls for &'a HNil, as if this was deliberately avoided for some reason. I wonder why? (maybe this was left over from some attempt to work around infinite recursion issues, which I recall tend to happen when dealing with impls on references?)

I'm still looking into the Foldable traits, which seem even more unusual; I'm not sure why there's an impl involving &'a F...


(Edit: Okay, there's good reason for the &'a F. It can be fixed with the addition of a helper trait as an implementation detail (and then users will no longer need to borrow the closure when calling foldr), but it would also make the documentation a bit more frightening. I'll hold off on suggesting this for now.)

Wrappers around trait methods (suggestions to improve ergonomics)

I find ergonomics to be a crucial part of any library's design, and I have some proposals for frunk that I would like to test the waters with.

I should preface this with some caveats:

  1. Not all of what I say is common practice. I'm just proposing design patterns that I've found to be successful in the past to see if frunk might be willing to adopt them.
  2. Ergonomical hacks often come at the cost of decreased learnability, in part because sometimes the abstraction may leak, and in part because it tends to lead to having multiple similar-looking things in the documentation. The impact of the latter can be reduced by having sufficient-quality docs that direct the reader to the right places and help clarify the different roles of similar-looking things.

I apologize if this is a lot to respond to! I just have strong opinions on this topic, and to really adopt frunk I'd like to know that these changes are either possible in the future, or that there is good reason for not making them.

Wrapping method traits with inherent methods

What

Something like this:

impl<A, Rest> HCons<A, Rest> {
    ...

    // NOTE: this is where the primary documentation for `get` would lie.
    // The docs on Selector would be reduced in scope to mostly just
    // describe it as a trait you can use in `where` bounds when you need
    // to call `get` in a generic function
    #[inline(always)]
    pub fn get<T, I>(&self) -> &T
    where Self: Selector<T, I>,
    { Selector::get(self) }

    #[inline(always)]
    pub fn pluck<T, I>(self) -> T
    where Self: Plucker<T, I>,
    { Plucker::pluck(self) }

    #[inline(always)]
    pub fn into_reverse(self) -> <Self as IntoReverse>::Output
    where Self: IntoReverse,
    { IntoReverse::into_reverse(self) }

    ...
}

impl HNil {
    ...
    // (same stuff; this can easily be done with a macro)
    ...
}

Aside: In funky I went even further, and tried to unify the documentation of HCons and HNil by using some trick to define a trait for documentation only (it cannot be used). At the end of the day though I'm not actually sure whether this makes the documentation better or worse, so for now I am not proposing that as an idea for frunk.

Why

Making turbofish usable

Existing motivation

Traits in a library like frunk are often forced to put type parameters on the trait rather than on the method. An example I added in PR #89 unfortunately showcases how awkward it can be to use a type annotation with coproduct.uninject when doing exhaustive matches:

let res: Result<i32, _> = co.uninject();
let co = match res {
    Ok(x) => return (2 * x) as f32,
    Err(co) => co,
};

(and the alternative of using UFCS is even worse)

If uninject were an inherent method designed with turbofish usage in mind, we could support the following usage:

let co = match co.uninject::<i32, _> {
    Ok(x) => return (2 * x) as f32,
    Err(co) => co,
};

Of course, there are still other ergonomic issues with uninject as evidenced by the Err(co) => co, but I feel that this is more a problem with the language itself.

Future motivation

Once I can provide sufficient evidence that the feature is useful, I'd like to add reified indices to frunk, so that you can do things like this:

let index = list_a.index_of::<T, >();
let value = list_b.at(index)

Imagine trying to do that without turbofish support! 😨

Less need to import traits

Having to use a glob import to use basic functionality of a type is, in my opinion, a design smell. use ::frunk::hlist::{HCons, NHil}; is all that a user should need to get 99% of the functionality of HLists.

In my ideal design, the only time it is necessary to import these traits should be if the user needs to write where bounds for a function that is generic over HLists. (actually, even then it is still not necessary, as they can use frunk::hlist::{self, HCons, HNil} and put where T: hlist::Sculptor<S, I> on the generic function).

Bonus 1: The import tree can be made shallower; since HCons and HNil are for the most part all you need, frunk can reexport those things at the root, allowing people to write use frunk::{HCons, HNil}.

Bonus 2: If we are willing to take the overhaul even further, we can get rid of prefixes like "Coprod" from the traits (e.g. Injector instead of CoprodInjector) now that we don't need to worry about name clashes. (I am assuming that is the reason why those prefixes were there...)

Wrapping "constructor" traits with free functions

CoprodInjector currently has this usability wart that you need to name the type of the coproduct in order to inject into it. This is a pain to deal with in cases where the type could otherwise be inferred. I think it would be ideal if this were exposed as a free function:

Before:

use frunk::*;

// even though only one thing uses this type,
// we still need to make a type alias so that we can inject into it later
type Gnarly = Coprod!(Oh, Dear, Thats, Something);

fn takes_gnarly(co: Gnarly) -> Something {
    ...
}

takes_gnarly(Gnarly::inject(dear))

After:

use frunk::coproduct;

fn takes_gnarly(co: Coprod!(Oh, Dear, Thats, Something)) -> Something {
    ...
}

// Now we can let type inference do its job, hurrah!
takes_gnarly(coproduct::inject(dear))

HList::length does not need to take a self parameter

Looked into porting the HList I use in gluon to the hlist in this crate. While I could port it there is a slight drawback in that the HList::length function in frunk requires a &self parameter which is not actually necessary. Could this perhaps be removed? (Also, should it perhaps be named len to mirror Vec, HashMap etc ?)

Generic for enums <-> Coproducts

So, obviously, structs are to HLists as enums are to Coproducts. Frunk has both of these data types, so why doesn't it implement Generic for enums yet?

After thinking about this a bit, it occurred to me that there's a nontrivial technical challenge here, due to the fact that rust doesn't consider enum variants to be types. Basically, we'd have to do something like one of the following:

  • we could generate our own structs for the enum variants
    • this raises questions about type parameters. (consider some type equivalent to Option<T>. Should the None type have type parameters? If so, does the user need to add phantom fields?)
  • we could restrict it to enums where all of the variants are newtype-like
  • other ideas welcome

Splitting coproducts, and upcasting to larger coproducts

Over the weekend I've been working on a reimplementation of frunk to help understand the current design and gather ideas. The following two ideas are really low hanging fruit; I mean, when I wrote them, I just figured that frunk obviously must already have something like these (but looking around, I don't think it does).

I can add something similar to frunk if others would like them.


Splitting coproducts

Split e.g. Coprod![A, B, C, D, E] into Result<Coprod![D, A, B], Coprod![C, E]>.

(In my repo I named it sculpt after the similar operation on HLists, but I'm not too sure about that name)

// Indices is usually inferred
pub trait CoprodSculptor<Targets, Indices>: Sized {
    type Remainder;

    fn sculpt(self) -> Result<Targets, Self::Remainder>;
}

The implementation is just repeated calls to uninject(). (.pluck() in my codebase)

Upcasting

Upcast e.g. Coprod![A, B, D] into Coprod![A, B, C, D, E].

// Indices is usually inferred
pub trait CoprodEmbedder<Choices, Indices> {
    fn embed(self) -> Choices;
}

My implementation of embed is insultingly simple, and if rust had generic closures it could be written as just coprod.fold(|x| Target::inject(x)). Some neat properties that fall out of this simple implementation are that order doesn't matter, and duplicate types in the input can map to the same place in the output (so Coprod![C, A, E] and Coprod![B, B, B, B] can also be converted into Coprod![A, B, C, D, E]).


Note: embed seems like it could be just a special case of sculpt where Remainder = CNil. But I am not certain. In my current implementation, the two methods have very different implementations and the indices take on completely different representations (indicated by the use of the newtype wrapper PopSeq that wraps the indices in implementations of sculpt).

Add Selector::get_mut

Thanks for the cool lib, I'm using it heavily to create a typesafe dataframe-like lib.

I have a need for a get_mut function on the Selector trait. Is there a reason this is missing/would you accept a PR?

combine_n should do exponentation by squaring

frunk/src/semigroup.rs

Lines 90 to 101 in bdb8b0f

/// Return this combined with itself `n` times.
pub fn combine_n<T>(o: &T, times: u32) -> T
where
T: Semigroup + Clone,
{
let mut x = o.clone();
// note: range is non-inclusive in the upper bound
for _ in 1..times {
x = o.combine(&x);
}
x
}

I'm honestly surprised to see that this doesn't do exponentation by squaring; to me, that has always been the point of having a function like combine_n

N.B. The correctness of using exponentation by squaring is guaranteed by the law of associativity.

to_cons for HList

When a function receives a HList trait object as a parameter, it's not possible to cast it back to a HCons. Consequently it's not possible to call the HCons-specific methods on it. There should be a method that, given a HList, can give you an Option<HCons<H, T>> or similar (with None used when the HList is actually HNil).

More effective use of semantic versioning

What I've seen so far during my time contributing is that frunk takes a fairly relaxed attitude towards introducing breaking changes, yet it is only at version 0.1.36.

This makes me concerned that breaking changes are perhaps frequently published as patch number upgrades. This can cause breakage in downstream crates, because by default, cargo will gladly download a crate with a larger patch number than specified in Cargo.toml.

I think the next release should be v0.2.0. 🙂

CNil::absurd() for producing any type

Dangit, I'm having too many ideas all at once now!

impl CNil {
    pub fn absurd<T>(self) -> T {
        match self { }
    }
}

Bikeshedding for the name is welcome. The name absurd() comes from proof solvers. Picture it appearing e.g. at the bottom of this example, or as the body of this impl. (random aside: uh... why is that impl #[doc(hidden)]?)

Getting field name from Labelled at runtime

Seems like a good idea, from Reddit

Current thought is that we can do this right now if we use HList to encode the Name type instead of tuple. It will return a String or &str though, instead of a static string..

Improve the documentation of Suffixed/LiftFrom/LiftInto

Logging an issue from discussion on #89.

The documentation of Suffix is entirely inadequate:

An index denoting that Suffix is just that.

At bare minimum it needs to link to LiftFrom/LiftInto, since otherwise the average person just happening upon it from e.g. the module page (a.k.a. me from two days ago) has absolutely no idea what they're even looking at.

LiftInto shows examples of larger hlists being created from smaller ones, but it's not clear from those examples what the rules and limitations are. LiftFrom doesn't even have examples like that.

hlist_pat can trigger #[warn(non_shorthand_field_patterns)]

let hlist_pat![head, ...tail] = list;
warning: the `head:` in this pattern is redundant
  --> src/main.rs:5:18
   |
5  |     => { HCons { head: $a, tail: $b }};
   |                  ^^^^ help: remove this
...
10 |     let hlist_pat!(head, tail) = list;
   |         ---------------------- in this macro invocation
   |
   = note: #[warn(non_shorthand_field_patterns)] on by default

warning: the `tail:` in this pattern is redundant
  --> src/main.rs:5:28
   |
5  |     => { HCons { head: $a, tail: $b }};
   |                            ^^^^ help: remove this
...
10 |     let hlist_pat!(head, tail) = list;
   |         ---------------------- in this macro invocation

    Finished dev [unoptimized + debuginfo] target(s) in 0.50 secs
     Running `target/debug/playground`

I don't see any solution so I opened this thread as a call for help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.