frunk/
lib.rs

1#![no_std]
2#![doc(html_playground_url = "https://play.rust-lang.org/")]
3//! Frunk: generic functional programming toolbelt for Rust
4//!
5//! Aims to be a collection of functional programming abstractions implemented in Rust
6//! in effective, useful, and idiomatic ways. Examples of things that are included in rust are:
7//!
8//!   1. HLists (heterogeneously-typed lists)
9//!   2. LabelledGeneric, and Generic
10//!   3. Coproduct
11//!   4. Validated (accumulator for Result)
12//!   5. Semigroup
13//!   6. Monoid
14//!
15#![cfg_attr(
16    feature = "alloc",
17    doc = r#"
18Here is a small taste of what Frunk has to offer:
19
20```
21# fn main() {
22use frunk::prelude::*;
23use frunk::{self, hlist, hlist_pat, LabelledGeneric, monoid, Semigroup, Generic};
24
25// Combining Monoids
26let v = vec![Some(1), Some(3)];
27assert_eq!(monoid::combine_all(&v), Some(4));
28
29// HLists
30let h = hlist![1, "hi"];
31assert_eq!(h.len(), 2);
32let hlist_pat!(a, b) = h;
33assert_eq!(a, 1);
34assert_eq!(b, "hi");
35
36let h1 = hlist![Some(1), 3.3, 53i64, "hello".to_owned()];
37let h2 = hlist![Some(2), 1.2, 1i64, " world".to_owned()];
38let h3 = hlist![Some(3), 4.5, 54, "hello world".to_owned()];
39assert_eq!(h1.combine(&h2), h3);
40
41// Generic and LabelledGeneric-based programming
42// Allows Structs to play well easily with HLists
43
44#[derive(Generic, LabelledGeneric)]
45struct ApiUser<'a> {
46    FirstName: &'a str,
47    LastName: &'a str,
48    Age: usize,
49}
50
51#[derive(Generic, LabelledGeneric)]
52struct NewUser<'a> {
53    first_name: &'a str,
54    last_name: &'a str,
55    age: usize,
56}
57
58#[derive(LabelledGeneric)]
59struct SavedUser<'a> {
60    first_name: &'a str,
61    last_name: &'a str,
62    age: usize,
63}
64
65// Instantiate a struct from an HList. Note that you can go the other way too.
66let a_user: ApiUser = frunk::from_generic(hlist!["Joe", "Blow", 30]);
67
68// Convert using Generic
69let n_user: NewUser = Generic::convert_from(a_user); // done
70
71// Convert using LabelledGeneric
72//
73// This will fail if the fields of the types converted to and from do not
74// have the same names or do not line up properly :)
75//
76// Also note that we're using a helper method to avoid having to use universal
77// function call syntax
78let s_user: SavedUser = frunk::labelled_convert_from(n_user);
79
80assert_eq!(s_user.first_name, "Joe");
81assert_eq!(s_user.last_name, "Blow");
82assert_eq!(s_user.age, 30);
83
84// Uh-oh ! last_name and first_name have been flipped!
85#[derive(LabelledGeneric)]
86struct DeletedUser<'a> {
87    last_name: &'a str,
88    first_name: &'a str,
89    age: usize,
90}
91// let d_user = <DeletedUser as LabelledGeneric>::convert_from(s_user); <-- this would fail at compile time :)
92
93// This will, however, work, because we make use of the Sculptor type-class
94// to type-safely reshape the representations to align/match each other.
95let d_user: DeletedUser = frunk::transform_from(s_user);
96assert_eq!(d_user.first_name, "Joe");
97# }
98```"#
99)]
100//!
101//! ##### Transmogrifying
102//!
103//! Sometimes you need might have one data type that is "similar in shape" to another data type, but it
104//! is similar _recursively_ (e.g. it has fields that are structs that have fields that are a superset of
105//! the fields in the target type, so they are transformable recursively).  `.transform_from` can't
106//! help you there because it doesn't deal with recursion, but the `Transmogrifier` can help if both
107//! are `LabelledGeneric` by `transmogrify()`ing from one to the other.
108//!
109//! What is "transmogrifying"? In this context, it means to recursively transform some data of type A
110//! into data of type B, in a typesafe way, as long as A and B are "similarly-shaped".  In other words,
111//! as long as B's fields and their subfields are subsets of A's fields and their respective subfields,
112//! then A can be turned into B.
113//!
114//! As usual, the goal with Frunk is to do this:
115//! * Using stable (so no specialisation, which would have been helpful, methinks)
116//! * Typesafe
117//! * No usage of `unsafe`
118//!
119//! Here is an example:
120//!
121//! ```rust
122//! # fn main() {
123//! use frunk::LabelledGeneric;
124//! use frunk::labelled::Transmogrifier;
125//!
126//! #[derive(LabelledGeneric)]
127//! struct InternalPhoneNumber {
128//!     emergency: Option<usize>,
129//!     main: usize,
130//!     secondary: Option<usize>,
131//! }
132//!
133//! #[derive(LabelledGeneric)]
134//! struct InternalAddress<'a> {
135//!     is_whitelisted: bool,
136//!     name: &'a str,
137//!     phone: InternalPhoneNumber,
138//! }
139//!
140//! #[derive(LabelledGeneric)]
141//! struct InternalUser<'a> {
142//!     name: &'a str,
143//!     age: usize,
144//!     address: InternalAddress<'a>,
145//!     is_banned: bool,
146//! }
147//!
148//! #[derive(LabelledGeneric, PartialEq, Debug)]
149//! struct ExternalPhoneNumber {
150//!     main: usize,
151//! }
152//!
153//! #[derive(LabelledGeneric, PartialEq, Debug)]
154//! struct ExternalAddress<'a> {
155//!     name: &'a str,
156//!     phone: ExternalPhoneNumber,
157//! }
158//!
159//! #[derive(LabelledGeneric, PartialEq, Debug)]
160//! struct ExternalUser<'a> {
161//!     age: usize,
162//!     address: ExternalAddress<'a>,
163//!     name: &'a str,
164//! }
165//!
166//! let internal_user = InternalUser {
167//!     name: "John",
168//!     age: 10,
169//!     address: InternalAddress {
170//!         is_whitelisted: true,
171//!         name: "somewhere out there",
172//!         phone: InternalPhoneNumber {
173//!             main: 1234,
174//!             secondary: None,
175//!             emergency: Some(5678),
176//!         },
177//!     },
178//!     is_banned: true,
179//! };
180//!
181//! /// Boilerplate-free conversion of a top-level InternalUser into an
182//! /// ExternalUser, taking care of subfield conversions as well.
183//! let external_user: ExternalUser = internal_user.transmogrify();
184//!
185//! let expected_external_user = ExternalUser {
186//!     name: "John",
187//!     age: 10,
188//!     address: ExternalAddress {
189//!         name: "somewhere out there",
190//!         phone: ExternalPhoneNumber {
191//!             main: 1234,
192//!         },
193//!     }
194//! };
195//!
196//! assert_eq!(external_user, expected_external_user);
197//! # }
198//! ```
199//!
200//! Links:
201//!   1. [Source on Github](https://github.com/lloydmeta/frunk)
202//!   2. [Crates.io page](https://crates.io/crates/frunk)
203
204#[cfg(feature = "alloc")]
205extern crate alloc;
206
207#[cfg(any(feature = "std", test))]
208extern crate std;
209
210pub mod monoid;
211pub mod semigroup;
212#[cfg(feature = "validated")]
213pub mod validated;
214
215pub use frunk_core::*;
216pub use frunk_derives::*;
217
218// Root-level reexports so that users don't need to guess where things are located.
219//
220// Things to re-export:
221//
222// * Datatypes and free functions intended for human consumption.
223//   * **Exception:** things that benefit from being namespaced,
224//     like `frunk::semigroup::Any`
225//
226// * Traits that users ought to care enough about to `use` it **by name:**
227//   * ...because users might want to `impl` it for their own types
228//   * ...because it shows up in lots of `where` bounds
229//   * NOT simply because it extends existing types with useful methods
230//     (that's what the prelude is for!)
231
232// NOTE: without `#[doc(no_inline)]`, rustdoc will generate two separate pages for
233//       each item (one in `frunk::` and another in `frunk_core::module::`).
234//       Hyperlinks will be broken for the ones in `frunk::`, so we need to prevent it.
235
236#[doc(no_inline)]
237pub use crate::hlist::lift_from;
238#[doc(no_inline)]
239pub use crate::hlist::HCons;
240#[doc(no_inline)]
241pub use crate::hlist::HNil;
242#[doc(no_inline)]
243pub use crate::traits::Func;
244#[doc(no_inline)]
245pub use crate::traits::Poly;
246#[doc(no_inline)]
247pub use crate::traits::{ToMut, ToRef}; // useful for where bounds
248
249#[doc(no_inline)]
250pub use crate::coproduct::Coproduct;
251
252#[doc(no_inline)]
253pub use crate::generic::convert_from;
254#[doc(no_inline)]
255pub use crate::generic::from_generic;
256#[doc(no_inline)]
257pub use crate::generic::into_generic;
258#[doc(no_inline)]
259pub use crate::generic::map_inter;
260#[doc(no_inline)]
261pub use crate::generic::map_repr;
262#[doc(no_inline)]
263pub use crate::generic::Generic;
264
265#[doc(no_inline)]
266pub use crate::labelled::from_labelled_generic;
267#[doc(no_inline)]
268pub use crate::labelled::into_labelled_generic;
269#[doc(no_inline)]
270pub use crate::labelled::labelled_convert_from;
271#[doc(no_inline)]
272pub use crate::labelled::transform_from;
273#[doc(no_inline)]
274pub use crate::labelled::LabelledGeneric;
275
276#[doc(no_inline)]
277pub use crate::semigroup::Semigroup;
278
279#[doc(no_inline)]
280pub use crate::monoid::Monoid;
281
282#[doc(no_inline)]
283#[cfg(feature = "validated")]
284pub use crate::validated::Validated;
285
286pub mod prelude {
287    //! Traits that need to be imported for the complete `frunk` experience.
288    //!
289    //! The intent here is that `use frunk::prelude::*` is enough to provide
290    //! access to any missing methods advertised in frunk's documentation.
291
292    #[doc(no_inline)]
293    pub use crate::hlist::HList; // for LEN
294    #[doc(no_inline)]
295    pub use crate::hlist::LiftFrom;
296    #[doc(no_inline)]
297    pub use crate::hlist::LiftInto;
298
299    #[doc(no_inline)]
300    #[cfg(feature = "validated")]
301    pub use crate::validated::IntoValidated;
302}