Skip to main content

fp_library/classes/
extract.rs

1//! Types that hold exactly one value which can be extracted.
2//!
3//! [`Extract`] is the inverse of [`Deferrable`](crate::classes::Deferrable): where
4//! `Deferrable` constructs a value lazily from a thunk, `Extract` forces/extracts
5//! the inner value. For types whose brand implements `Extract` (e.g.,
6//! [`ThunkBrand`](crate::brands::ThunkBrand)), `extract(defer(|| x)) == x`
7//! forms a round-trip. Note that `Extract` is a brand-level trait (implemented
8//! by `ThunkBrand`), while `Deferrable` is a value-level trait (implemented by
9//! concrete types like [`Thunk`](crate::types::Thunk)).
10//!
11//! This trait is used by [`Free::evaluate`](crate::types::Free::evaluate) to
12//! execute the effects in a [`Free`](crate::types::Free) monad.
13//!
14//! ### Examples
15//!
16//! ```
17//! use fp_library::{
18//! 	brands::*,
19//! 	functions::*,
20//! 	types::*,
21//! };
22//!
23//! let thunk = Thunk::new(|| 42);
24//! assert_eq!(extract::<ThunkBrand, _>(thunk), 42);
25//! ```
26
27#[fp_macros::document_module]
28mod inner {
29	use {
30		crate::kinds::*,
31		fp_macros::*,
32	};
33
34	/// A type containing exactly one extractable value, providing a natural
35	/// transformation `F ~> Id`.
36	///
37	/// This trait witnesses that a type always holds a single value that can be
38	/// extracted by running its effect. It is used by
39	/// [`Free::evaluate`](crate::types::Free::evaluate) to execute the effects in a
40	/// [`Free`](crate::types::Free) monad.
41	///
42	/// `Extract` is the inverse of [`Deferrable`](crate::classes::Deferrable):
43	/// `Deferrable` constructs lazy values from thunks, while `Extract` forces and
44	/// extracts them. For types whose brand implements `Extract` (e.g.,
45	/// `ThunkBrand`), the round-trip law `extract(defer(|| x)) == x` holds.
46	///
47	/// Implemented by types that always contain exactly one value and can
48	/// surrender ownership of it. [`Lazy`](crate::types::Lazy) cannot implement
49	/// this trait because forcing it returns `&A` (a reference), not an owned `A`.
50	/// [`Trampoline`](crate::types::Trampoline) does not have a brand and therefore
51	/// cannot participate in HKT traits.
52	///
53	/// # Laws
54	///
55	/// **Pure-extract:** extracting a pure value returns the original value.
56	/// For any `x: A`:
57	///
58	/// ```text
59	/// extract(pure(x)) == x
60	/// ```
61	///
62	/// This law states that wrapping a value in the type and immediately
63	/// extracting it is the identity.
64	#[kind(type Of<'a, A: 'a>: 'a;)]
65	pub trait Extract {
66		/// Extracts the inner value.
67		#[document_signature]
68		///
69		#[document_type_parameters(
70			"The lifetime of the value.",
71			"The type of the value inside the container."
72		)]
73		///
74		#[document_parameters("The container to extract from.")]
75		///
76		#[document_returns("The inner value.")]
77		#[document_examples]
78		///
79		/// ```
80		/// use fp_library::{
81		/// 	brands::*,
82		/// 	functions::*,
83		/// 	types::*,
84		/// };
85		///
86		/// let thunk = Thunk::new(|| 42);
87		/// assert_eq!(extract::<ThunkBrand, _>(thunk), 42);
88		/// ```
89		fn extract<'a, A: 'a>(
90			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
91		) -> A;
92	}
93
94	/// Extracts the inner value.
95	///
96	/// Free function version that dispatches to [the type class' associated function][`Extract::extract`].
97	#[document_signature]
98	///
99	#[document_type_parameters(
100		"The lifetime of the value.",
101		"The extractable type.",
102		"The type of the value inside the container."
103	)]
104	///
105	#[document_parameters("The container to extract from.")]
106	///
107	#[document_returns("The inner value.")]
108	#[document_examples]
109	///
110	/// ```
111	/// use fp_library::{
112	/// 	brands::*,
113	/// 	functions::*,
114	/// 	types::*,
115	/// };
116	///
117	/// let thunk = Thunk::new(|| 42);
118	/// assert_eq!(extract::<ThunkBrand, _>(thunk), 42);
119	/// ```
120	pub fn extract<'a, F, A>(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> A
121	where
122		F: Extract,
123		A: 'a, {
124		F::extract(fa)
125	}
126}
127
128pub use inner::*;
129
130#[cfg(test)]
131mod tests {
132	use {
133		crate::{
134			brands::*,
135			functions::*,
136			types::*,
137		},
138		quickcheck_macros::quickcheck,
139	};
140
141	/// Extract pure-extract law: extract(pure(x)) == x.
142	#[quickcheck]
143	fn prop_extract_pure(x: i32) -> bool {
144		let fa = Thunk::pure(x);
145		extract::<ThunkBrand, _>(fa) == x
146	}
147}