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}