diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d7c8083..4b42143b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -146,4 +146,4 @@ jobs: RUSTDOCFLAGS: '-D warnings' with: command: doc - args: --all --no-deps --document-private-items + args: --all --no-deps --document-private-items --features typenum diff --git a/.github/workflows/rustdoc.yml b/.github/workflows/rustdoc.yml index f61a8ab5..83923d17 100644 --- a/.github/workflows/rustdoc.yml +++ b/.github/workflows/rustdoc.yml @@ -24,7 +24,7 @@ jobs: name: build docs with: command: doc - args: --all --no-deps + args: --all --no-deps --features typenum - uses: peaceiris/actions-gh-pages@v4 name: push docs diff --git a/Cargo.toml b/Cargo.toml index dab64399..b1e3cdb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frunk" edition = "2021" -version = "0.4.2" +version = "0.5.0" authors = ["Lloyd <lloydmeta@gmail.com>"] description = "Frunk provides developers with a number of functional programming tools like HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid, Semigroup and friends." license = "MIT" @@ -19,7 +19,7 @@ time = "0.3" [dependencies.frunk_core] path = "core" default-features = false -version = "0.4.2" +version = "0.5.0" [dependencies.frunk_proc_macros] path = "proc-macros" @@ -45,6 +45,7 @@ default = ["validated", "proc-macros"] validated = ["std"] proc-macros = ["frunk_proc_macros"] std = ["frunk_core/std"] +typenum = ["frunk_core/typenum"] [profile.bench] opt-level = 3 diff --git a/core/Cargo.toml b/core/Cargo.toml index afc58b1b..bb8dbc97 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "frunk_core" edition = "2021" -version = "0.4.2" +version = "0.5.0" authors = ["Lloyd <lloydmeta@gmail.com>"] description = "Frunk core provides developers with HList, Coproduct, LabelledGeneric and Generic" license = "MIT" @@ -15,9 +15,11 @@ travis-ci = { repository = "lloydmeta/frunk" } [features] default = ["std"] std = [] +typenum = ["dep:typenum"] [dependencies] serde = { version = "^1.0", optional = true, features = [ "derive" ] } +typenum = { version = "1.17.0", optional = true } [dev-dependencies.frunk_derives] path = "../derives" @@ -27,7 +29,7 @@ version = "0.4.1" [dev-dependencies.frunk] path = ".." default-features = false -version = "0.4.2" +version = "0.5.0" [dev-dependencies.frunk_proc_macros] path = "../proc-macros" diff --git a/core/src/hlist.rs b/core/src/hlist.rs index 2fdee009..5284ae29 100644 --- a/core/src/hlist.rs +++ b/core/src/hlist.rs @@ -60,6 +60,11 @@ use crate::traits::{Func, IntoReverse, Poly, ToMut, ToRef}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(feature = "typenum")] +pub use typenum; +#[cfg(feature = "typenum")] +use typenum::{bit::B1, Add1, Unsigned, U0}; + use std::ops::Add; /// Typeclass for HList-y behaviour @@ -67,18 +72,58 @@ use std::ops::Add; /// An HList is a heterogeneous list, one that is statically typed at compile time. In simple terms, /// it is just an arbitrarily-nested Tuple2. pub trait HList: Sized { - /// Returns the length of a given HList type without making use of any references, or - /// in fact, any values at all. + /// The type-level encapsulation of the lists length, using `typenum` under the hood. /// /// # Examples /// ``` /// # fn main() { + /// use frunk_core::hlist::typenum::{self, Unsigned }; /// use frunk::prelude::*; /// use frunk_core::HList; /// - /// assert_eq!(<HList![i32, bool, f32]>::LEN, 3); + /// type LenThree = HList![bool, (), u8]; + /// type LenTwo = HList![bool, ()]; + /// + /// // Attach a constraint that ensures constraints are met at type-check time + /// fn type_len_constraint<T: typenum::IsLess<typenum::U3, Output = typenum::True>>() {} + /// + /// + /// + /// // Won't compile: the length of LenThree doesn't meet the less-than-3 requirement + /// // let _fail = type_len_constraint::<<LenThree as HList>::Len>(); + /// let _is_good = type_len_constraint::<<LenTwo as HList>::Len>(); + /// + /// // Pull out the length of the list in the word-size of your choosing. + /// let byte: u8 = <<LenThree as HList>::Len>::U8; + /// let u_16: u16 = <<LenThree as HList>::Len>::U16; + /// + /// + /// assert_eq!(<LenThree as HList>::Len::U8, 3u8); + /// # } + /// ``` + /// + /// ```compile_fail + /// # fn main() { + /// use frunk_core::hlist::typenum::{self, Unsigned }; + /// use frunk::prelude::*; + /// use frunk_core::HList; + /// + /// type LenThree = HList![bool, (), u8]; + /// type LenTwo = HList![bool, ()]; + /// + /// // Attach a constraint that ensures constraints are met at type-check time + /// fn type_len_constraint<T: typenum::IsLess<typenum::U3, Output = typenum::True>>() {} + /// + /// + /// + /// // Won't compile: the length of LenThree doesn't meet the less-than-3 requirement + /// let _ = type_len_constraint::<<LenThree as HList>::Len>(); /// # } /// ``` + #[cfg(feature = "typenum")] + type Len: Unsigned; + + /// Length as a usize const generic. Is equivilent to `<Self as HList>::LEN::USIZE` const LEN: usize; /// Returns the length of a given HList @@ -115,21 +160,6 @@ pub trait HList: Sized { Self::LEN == 0 } - /// Returns the length of a given HList type without making use of any references, or - /// in fact, any values at all. - /// - /// # Examples - /// ``` - /// # fn main() { - /// use frunk::prelude::*; - /// use frunk_core::HList; - /// - /// assert_eq!(<HList![i32, bool, f32]>::static_len(), 3); - /// # } - /// ``` - #[deprecated(since = "0.1.31", note = "Please use LEN instead")] - fn static_len() -> usize; - /// Prepends an item to the current HList /// /// # Examples @@ -168,10 +198,10 @@ pub trait HList: Sized { pub struct HNil; impl HList for HNil { + #[cfg(feature = "typenum")] + type Len = U0; + const LEN: usize = 0; - fn static_len() -> usize { - Self::LEN - } } /// Represents the most basic non-empty HList. Its value is held in `head` @@ -183,11 +213,18 @@ pub struct HCons<H, T> { pub tail: T, } +#[cfg(feature = "typenum")] +impl<H, T: HList> HList for HCons<H, T> +where + <T as HList>::Len: Add<B1>, + <<T as HList>::Len as Add<B1>>::Output: Unsigned, +{ + type Len = <<T as HList>::Len as Add<B1>>::Output; + const LEN: usize = 1 + <T as HList>::LEN; +} +#[cfg(not(feature = "typenum"))] impl<H, T: HList> HList for HCons<H, T> { const LEN: usize = 1 + <T as HList>::LEN; - fn static_len() -> usize { - Self::LEN - } } impl<H, T> HCons<H, T> { @@ -1120,9 +1157,25 @@ impl HZippable<HNil> for HNil { } } +#[cfg(not(feature = "typenum"))] +impl<H1, T1, H2, T2> HZippable<HCons<H2, T2>> for HCons<H1, T1> +where + T1: HZippable<T2>, +{ + type Zipped = HCons<(H1, H2), T1::Zipped>; + fn zip(self, other: HCons<H2, T2>) -> Self::Zipped { + HCons { + head: (self.head, other.head), + tail: self.tail.zip(other.tail), + } + } +} +#[cfg(feature = "typenum")] impl<H1, T1, H2, T2> HZippable<HCons<H2, T2>> for HCons<H1, T1> where T1: HZippable<T2>, + <<T1 as HZippable<T2>>::Zipped as HList>::Len: Add<B1>, + Add1<<<T1 as HZippable<T2>>::Zipped as HList>::Len>: Unsigned, { type Zipped = HCons<(H1, H2), T1::Zipped>; fn zip(self, other: HCons<H2, T2>) -> Self::Zipped { @@ -1433,6 +1486,26 @@ where } } +#[cfg(feature = "typenum")] +#[cfg(feature = "std")] +#[allow(clippy::from_over_into)] +impl<H, Tail> Into<Vec<H>> for HCons<H, Tail> +where + Tail: Into<Vec<H>> + HList, + <Tail as HList>::Len: Add<B1>, + Add1<<Tail as HList>::Len>: Unsigned, +{ + fn into(self) -> Vec<H> { + let h = self.head; + let t = self.tail; + let mut v = Vec::with_capacity(<Self as HList>::LEN); + v.push(h); + let mut t_vec: Vec<H> = t.into(); + v.append(&mut t_vec); + v + } +} +#[cfg(not(feature = "typenum"))] #[cfg(feature = "std")] #[allow(clippy::from_over_into)] impl<H, Tail> Into<Vec<H>> for HCons<H, Tail> @@ -1905,6 +1978,8 @@ mod tests { #[test] fn test_len_const() { + #[cfg(feature = "typenum")] + assert_eq!(<HList![usize, &str, f32] as HList>::Len::USIZE, 3); assert_eq!(<HList![usize, &str, f32] as HList>::LEN, 3); } diff --git a/laws/Cargo.toml b/laws/Cargo.toml index 576948b6..42280264 100644 --- a/laws/Cargo.toml +++ b/laws/Cargo.toml @@ -15,7 +15,7 @@ travis-ci = { repository = "lloydmeta/frunk" } [dependencies.frunk] path = ".." default-features = false -version = "0.4.2" +version = "0.5.0" [dependencies] quickcheck = "1.0.3" diff --git a/proc-macro-helpers/Cargo.toml b/proc-macro-helpers/Cargo.toml index a58542f5..67fd7558 100644 --- a/proc-macro-helpers/Cargo.toml +++ b/proc-macro-helpers/Cargo.toml @@ -20,4 +20,4 @@ proc-macro2 = "1" [dependencies.frunk_core] path = "../core" default-features = false -version = "0.4.2" +version = "0.5.0" diff --git a/proc-macros/Cargo.toml b/proc-macros/Cargo.toml index b190ef27..1c2b3070 100644 --- a/proc-macros/Cargo.toml +++ b/proc-macros/Cargo.toml @@ -23,7 +23,7 @@ proc-macro = true [dependencies.frunk_core] path = "../core" default-features = false -version = "0.4.2" +version = "0.5.0" [dependencies.frunk_proc_macro_helpers] path = "../proc-macro-helpers"