From 03325288e6ea31580e2216775c16f675f8f8fe6a Mon Sep 17 00:00:00 2001 From: Lloyd Date: Fri, 24 Mar 2017 16:01:34 +0900 Subject: [PATCH 1/8] Add Laws project (#46) * Start adding Laws submodule * Add Semigroup laws * Add laws test to Travis * Add tests for more Semigroup instances * Try to fix paths in Travis.yml * Go back to the main directory for running cargo doc * Better docs * Add tests for Moniod left right identity laws * fix typo --- .travis.yml | 5 +- Cargo.toml | 6 +- laws/Cargo.toml | 20 +++++ laws/src/lib.rs | 12 +++ laws/src/monoid_laws.rs | 151 +++++++++++++++++++++++++++++++++++++ laws/src/semigroup_laws.rs | 103 +++++++++++++++++++++++++ laws/src/wrapper.rs | 56 ++++++++++++++ src/monoid.rs | 2 +- 8 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 laws/Cargo.toml create mode 100644 laws/src/lib.rs create mode 100644 laws/src/monoid_laws.rs create mode 100644 laws/src/semigroup_laws.rs create mode 100644 laws/src/wrapper.rs diff --git a/.travis.yml b/.travis.yml index 943864703..a907d3dcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,8 +15,9 @@ script: - | travis-cargo build && travis-cargo test && - cd core && travis-cargo test && - travis-cargo doc + cd ${TRAVIS_BUILD_DIR}/core && travis-cargo test && + cd ${TRAVIS_BUILD_DIR}/laws && travis-cargo test && + cd ${TRAVIS_BUILD_DIR} && travis-cargo doc after_success: - travis-cargo doc-upload diff --git a/Cargo.toml b/Cargo.toml index 53403fd69..539d8b242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,8 @@ version = "0.0.11" [dependencies.frunk_derives] path = "derives" -version = "0.0.12" \ No newline at end of file +version = "0.0.12" + +[dev-dependencies.frunk_laws] +path = "laws" +version = "0.0.1" \ No newline at end of file diff --git a/laws/Cargo.toml b/laws/Cargo.toml new file mode 100644 index 000000000..de62b3dcb --- /dev/null +++ b/laws/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "frunk_laws" +version = "0.0.1" +authors = ["Lloyd "] +description = "frunk_laws contains laws for algebras declared in Frunk." +license = "MIT" +documentation = "https://docs.rs/frunk_laws" +repository = "https://github.com/lloydmeta/frunk" +keywords = ["Frunk", "laws", "testing", "algebras"] + +[badges] +travis-ci = { repository = "lloydmeta/frunk" } + +[dependencies.frunk] +path = ".." +version = "0.1.12" + + +[dependencies] +quickcheck = "0.3" \ No newline at end of file diff --git a/laws/src/lib.rs b/laws/src/lib.rs new file mode 100644 index 000000000..d2b7ed7e9 --- /dev/null +++ b/laws/src/lib.rs @@ -0,0 +1,12 @@ +#![doc(html_playground_url = "https://play.rust-lang.org/")] +//! Frunk Laws +//! +//! This library contains laws that can be used to test the implementation of +//! algebras declared in Frunk + +extern crate frunk; +extern crate quickcheck; + +pub mod semigroup_laws; +pub mod monoid_laws; +pub mod wrapper; diff --git a/laws/src/monoid_laws.rs b/laws/src/monoid_laws.rs new file mode 100644 index 000000000..a93c8beca --- /dev/null +++ b/laws/src/monoid_laws.rs @@ -0,0 +1,151 @@ +//! Module that holds laws for Monoid implementations +//! +//! Note that you should use the semigroup_laws module to get the associative +//! law test. +//! +//! # Examples +//! +//! ``` +//! # extern crate quickcheck; +//! # extern crate frunk_laws; +//! # extern crate frunk; +//! # use quickcheck::quickcheck; +//! # use frunk::semigroup::*; +//! # fn main() { +//! use frunk_laws::monoid_laws::*; +//! quickcheck(left_identity as fn(String) -> bool); +//! quickcheck(right_identity as fn(String) -> bool); +//! # } +//! ``` +use frunk::monoid::*; + +/// Left identity law +/// +/// mempty <> x = x +/// +/// # Examples +/// +/// ``` +/// # extern crate quickcheck; +/// # extern crate frunk_laws; +/// # extern crate frunk; +/// # use quickcheck::quickcheck; +/// # use frunk::semigroup::*; +/// # fn main() { +/// use frunk_laws::monoid_laws::*; +/// quickcheck(left_identity as fn(String) -> bool); +/// # } +/// ``` +pub fn left_identity(a: A) -> bool { + ::empty().combine(&a) == a +} + +/// Right identity law +/// +/// x <> mempty = x +/// # Examples +/// +/// ``` +/// # extern crate quickcheck; +/// # extern crate frunk_laws; +/// # extern crate frunk; +/// # use quickcheck::quickcheck; +/// # use frunk::semigroup::*; +/// # fn main() { +/// use frunk_laws::monoid_laws::*; +/// quickcheck(right_identity as fn(String) -> bool); +/// # } +/// ``` +pub fn right_identity(a: A) -> bool { + a.combine(&::empty()) == a +} + + + +#[cfg(test)] +mod tests { + use super::*; + use wrapper::*; + use frunk::semigroup::*; + use quickcheck::quickcheck; + use std::collections::{HashSet, HashMap}; + + #[test] + fn string_id_prop() { + quickcheck(left_identity as fn(String) -> bool); + quickcheck(right_identity as fn(String) -> bool); + } + + #[test] + fn option_id_prop() { + quickcheck(left_identity as fn(Option) -> bool); + quickcheck(right_identity as fn(Option) -> bool); + } + + #[test] + fn vec_id_prop() { + quickcheck(left_identity as fn(Vec) -> bool); + quickcheck(right_identity as fn(Vec) -> bool); + } + + #[test] + fn hashset_id_prop() { + quickcheck(left_identity as fn(HashSet) -> bool); + quickcheck(right_identity as fn(HashSet) -> bool); + } + + #[test] + fn hashmap_id_prop() { + quickcheck(left_identity as fn(HashMap) -> bool); + quickcheck(right_identity as fn(HashMap) -> bool); + } + + #[test] + fn any_id_prop() { + quickcheck(left_identity as fn(Wrapper>) -> bool); + quickcheck(right_identity as fn(Wrapper>) -> bool); + } + + #[test] + fn all_id_prop() { + quickcheck(left_identity as fn(Wrapper>) -> bool); + quickcheck(right_identity as fn(Wrapper>) -> bool); + } + + macro_rules! numeric_id_props { + ($($id: ident; $tr:ty,)*) => { + + $( + #[test] + fn $id() { + quickcheck(left_identity as fn($tr) -> bool); + quickcheck(right_identity as fn($tr) -> bool); + } + )* + } + } + + numeric_id_props! { + i8_id_prop; i8, + product_i8_id_prop; Wrapper>, + u8_id_prop; u8, + product_u8_id_prop; Wrapper>, + i16_id_prop; i16, + product_i16_id_prop; Wrapper>, + u16_id_prop; u16, + product_u16_id_prop; Wrapper>, + i32_id_prop; i32, + product_i32_id_prop; Wrapper>, + u32_id_prop; u32, + product_u32_id_prop; Wrapper>, + i64_id_prop; i64, + product_i64_id_prop; Wrapper>, + u64_id_prop; u64, + product_u64_id_prop; Wrapper>, + isize_id_prop; isize, + product_isize_id_prop; Wrapper>, + usize_id_prop; usize, + product_usize_id_prop; Wrapper>, + } + +} diff --git a/laws/src/semigroup_laws.rs b/laws/src/semigroup_laws.rs new file mode 100644 index 000000000..e72ba15bb --- /dev/null +++ b/laws/src/semigroup_laws.rs @@ -0,0 +1,103 @@ +//! Module that holds laws for Semigroup implementations +//! +//! # Examples +//! +//! ``` +//! # extern crate quickcheck; +//! # extern crate frunk_laws; +//! # extern crate frunk; +//! # use quickcheck::quickcheck; +//! # use frunk::semigroup::*; +//! # fn main() { +//! use frunk_laws::semigroup_laws::*; +//! quickcheck(associativity as fn(Vec, Vec, Vec) -> bool) +//! # } +//! ``` + +use frunk::semigroup::*; + +/// Function for checking adherence to the associativity law +/// +/// (x <> y) <> z = x <> (y <> z) +/// +/// # Examples +/// +/// ``` +/// # extern crate quickcheck; +/// # extern crate frunk_laws; +/// # extern crate frunk; +/// # use quickcheck::quickcheck; +/// # use frunk::semigroup::*; +/// # fn main() { +/// use frunk_laws::semigroup_laws::*; +/// quickcheck(associativity as fn(Vec, Vec, Vec) -> bool) +/// # } +/// ``` +pub fn associativity(a: A, b: A, c: A) -> bool { + a.combine(&b).combine(&c) == a.combine(&b.combine(&c)) +} + + +#[cfg(test)] +mod tests { + use super::*; + use wrapper::*; + use quickcheck::quickcheck; + use std::collections::{HashSet, HashMap}; + + #[test] + fn string_prop() { + quickcheck(associativity as fn(String, String, String) -> bool) + } + + #[test] + fn option_prop() { + quickcheck(associativity as fn(Option, Option, Option) -> bool) + } + + #[test] + fn vec_prop() { + quickcheck(associativity as fn(Vec, Vec, Vec) -> bool) + } + + #[test] + fn hashset_prop() { + quickcheck(associativity as fn(HashSet, HashSet, HashSet) -> bool) + } + + #[test] + fn hashmap_prop() { + quickcheck(associativity as + fn(HashMap, + HashMap, + HashMap) + -> bool) + } + + #[test] + fn max_prop() { + quickcheck(associativity as + fn(Wrapper>, Wrapper>, Wrapper>) -> bool) + } + + #[test] + fn min_prop() { + quickcheck(associativity as + fn(Wrapper>, Wrapper>, Wrapper>) -> bool) + } + + #[test] + fn any_prop() { + quickcheck(associativity as + fn(Wrapper>, Wrapper>, Wrapper>) -> bool) + } + + #[test] + fn all_prop() { + quickcheck(associativity as + fn(Wrapper>, Wrapper>, Wrapper>) -> bool) + } + + + +} diff --git a/laws/src/wrapper.rs b/laws/src/wrapper.rs new file mode 100644 index 000000000..0193fa39a --- /dev/null +++ b/laws/src/wrapper.rs @@ -0,0 +1,56 @@ +//! This module holds the Wrapper newtype; used to write +//! instances of typeclasses that we don't define for types we don't +//! own + +use frunk::semigroup::*; +use frunk::monoid::*; +use quickcheck::*; + +/// The Wrapper NewType. Used for writing implementations of traits +/// that we don't own for type we don't own. +/// +/// Avoids the orphan typeclass instances problem in Haskell. +#[derive(Eq, PartialEq, PartialOrd, Debug, Clone)] +pub struct Wrapper(A); + +impl Arbitrary for Wrapper> { + fn arbitrary(g: &mut G) -> Self { + Wrapper(Max(Arbitrary::arbitrary(g))) + } +} + +impl Arbitrary for Wrapper> { + fn arbitrary(g: &mut G) -> Self { + Wrapper(Min(Arbitrary::arbitrary(g))) + } +} + +impl Arbitrary for Wrapper> { + fn arbitrary(g: &mut G) -> Self { + Wrapper(All(Arbitrary::arbitrary(g))) + } +} + +impl Arbitrary for Wrapper> { + fn arbitrary(g: &mut G) -> Self { + Wrapper(Any(Arbitrary::arbitrary(g))) + } +} + +impl Arbitrary for Wrapper> { + fn arbitrary(g: &mut G) -> Self { + Wrapper(Product(Arbitrary::arbitrary(g))) + } +} + +impl Semigroup for Wrapper { + fn combine(&self, other: &Self) -> Self { + Wrapper(self.0.combine(&other.0)) + } +} + +impl Monoid for Wrapper { + fn empty() -> Self { + Wrapper(::empty()) + } +} diff --git a/src/monoid.rs b/src/monoid.rs index a592f34c8..ae89cdb6a 100644 --- a/src/monoid.rs +++ b/src/monoid.rs @@ -34,7 +34,7 @@ use super::semigroup::{Semigroup, Product, All, Any}; use std::collections::*; use std::hash::Hash; -/// A Monoid is a Sempigroup that has an empty/ zero value +/// A Monoid is a Semigroup that has an empty/ zero value pub trait Monoid: Semigroup { /// For a given Monoid, returns its empty/zero value /// From 4e8522202c32193e55d3eb3b714845bc8e5f3635 Mon Sep 17 00:00:00 2001 From: Lloyd Date: Wed, 19 Apr 2017 02:24:12 +0900 Subject: [PATCH 2/8] Feature/coproduct (#47) * Basic building of a Coproduct * Add Coproduct! Type macro * Add Coproduct Fold * Add rustdocs --- README.md | 45 ++++++- core/src/hlist.rs | 6 +- src/coproduct.rs | 280 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 7 +- tests/generic_tests.rs | 3 +- tests/labelled_tests.rs | 3 +- tests/validated_tests.rs | 2 +- 7 files changed, 330 insertions(+), 16 deletions(-) create mode 100644 src/coproduct.rs diff --git a/README.md b/README.md index b82af6d10..653baadb7 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,13 @@ For a deep dive, RustDocs are available for: 1. [HList](#hlist) 2. [Generic](#generic) * 2.1 [LabelledGeneric](#labelledgeneric) -3. [Validated](#validated) -4. [Semigroup](#semigroup) -5. [Monoid](#monoid) -6. [Todo](#todo) -7. [Contributing](#contributing) -8. [Inspirations](#inspirations) +3. [Coproduct](#coproduct) +4. [Validated](#validated) +5. [Semigroup](#semigroup) +6. [Monoid](#monoid) +7. [Todo](#todo) +8. [Contributing](#contributing) +9. [Inspirations](#inspirations) ## Examples @@ -270,6 +271,38 @@ let d_user: DeletedUser = transform_from(s_user); For more information how Generic and Field work, check out their respective Rustdocs: * [Generic](https://beachape.com/frunk/frunk_core/generic/index.html) * [Labelled](https://beachape.com/frunk/frunk_core/labelled/index.html) + +### 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: + +```rust +#[macro_use] extern crate frunk; // for the Coproduct! type macro +use frunk::coproduct::*; + +// Declare the types we want in our Coproduct +type I32Bool = Coproduct!(i32, f32, bool); + +let co1: I32Bool = into_coproduct(3); +let get_from_1a: Option<&i32> = co1.get(); +let get_from_1b: Option<&bool> = co1.get(); +// This will fail at compile time because i8 is not in our Coproduct type +// let get_from_1b: Option<&i8> = 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); + +// We can fold our Coproduct into a single value by handling all cases +let folder = hlist![|i| format!("int {}", i), + |f| format!("float {}", f), + |b| (if b { "t" } else { "f" }).to_string()]; +assert_eq!(co1.fold(&folder), "int 3".to_string()); +``` + +For more information, check out the []docs for Coproduct](https://beachape.com/frunk/frunk/coproduct/index.html) ### Validated diff --git a/core/src/hlist.rs b/core/src/hlist.rs index aa24616e1..841333293 100644 --- a/core/src/hlist.rs +++ b/core/src/hlist.rs @@ -389,7 +389,8 @@ impl Plucker> for HC type Remainder = HCons>::Remainder>; fn pluck(self) -> (FromTail, Self::Remainder) { - let (target, tail_remainder): (FromTail, >::Remainder) = + let (target, tail_remainder): (FromTail, + >::Remainder) = >::pluck(self.tail); (target, HCons { @@ -661,7 +662,8 @@ impl HFoldLeftable>::Output; fn foldl(self, folder: HCons, acc: Acc) -> Self::Output { - self.tail.foldl(folder.tail, (folder.head)(acc, self.head)) + self.tail + .foldl(folder.tail, (folder.head)(acc, self.head)) } } diff --git a/src/coproduct.rs b/src/coproduct.rs new file mode 100644 index 000000000..a331171b1 --- /dev/null +++ b/src/coproduct.rs @@ -0,0 +1,280 @@ +//! Module that holds Coproduct data structures, traits, and implementations +//! +//! Think of "Coproduct" as ad-hoc enums; allowing you to do something like this +//! +//! ``` +//! # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { +//! type I32Bool = Coproduct!(i32, bool); +//! let co1: I32Bool = into_coproduct(3); +//! let co2: I32Bool = into_coproduct(true); +//! +//! // Getting stuff +//! let get_from_1a: Option<&i32> = co1.get(); +//! let get_from_1b: Option<&bool> = co1.get(); +//! assert_eq!(get_from_1a, Some(&3)); +//! assert_eq!(get_from_1b, None); +//! +//! let get_from_2a: Option<&i32> = co2.get(); +//! let get_from_2b: Option<&bool> = co2.get(); +//! assert_eq!(get_from_2a, None); +//! assert_eq!(get_from_2b, Some(&true)); +//! # } +//! ``` +//! +//! Or, if you want to "fold" over all possible values of a coproduct +//! +//! ``` +//! # #[macro_use] extern crate frunk; +//! # #[macro_use] extern crate frunk_core; +//! # use frunk::hlist::*; +//! # use frunk::coproduct::*; fn main() { +//! # type I32Bool = Coproduct!(i32, bool); +//! # let co1: I32Bool = into_coproduct(3); +//! # let co2: I32Bool = into_coproduct(true); +//! +//! let folder = hlist![ +//! |i| format!("i32 {}", i), +//! |b| String::from(if b { "t" } else { "f" }) +//! ]; +//! +//! assert_eq!(co1.fold(&folder), "i32 3".to_string()); +//! assert_eq!(co2.fold(&folder), "t".to_string()); +//! # } +//! ``` + +use frunk_core::hlist::*; + +/// Enum type representing a Coproduct. Think of this as a Result, but capable +/// of supporting any arbitrary number of types instead of just 2. +/// +/// To consctruct a Coproduct, you would typically declare a type using the `Coproduct!` type +/// macro and then use the `into_coproduct` method. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { +/// type I32Bool = Coproduct!(i32, bool); +/// let co1: I32Bool = into_coproduct(3); +/// let get_from_1a: Option<&i32> = co1.get(); +/// let get_from_1b: Option<&bool> = co1.get(); +/// assert_eq!(get_from_1a, Some(&3)); +/// assert_eq!(get_from_1b, None); +/// # } +/// ``` +#[derive(PartialEq, Debug, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum Coproduct { + /// Coproduct is either H or T, in this case, it is H + Inl(H), + /// Coproduct is either H or T, in this case, it is T + Inr(T), +} + +/// Phantom type for signature purposes only (has no value) +/// +/// Used by the macro to terminate the Coproduct type signature +#[derive(PartialEq, Debug, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum CNil {} + +/// Returns a type signature for a Coproduct of the provided types +/// +/// This is a type macro (introduced in Rust 1.13) that makes it easier +/// to write nested type signatures. +/// +/// # Examples +/// +/// ``` +/// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { +/// type I32Bool = Coproduct!(i32, bool); +/// let co1: I32Bool = into_coproduct(3); +/// # } +/// ``` +#[macro_export] +macro_rules! Coproduct { + // Nothing + () => { $crate::coproduct::CNil }; + + // Just a single item + ($single: ty) => { + $crate::coproduct::Coproduct<$single, CNil> + }; + + ($first: ty, $( $repeated: ty ), +) => { + $crate::coproduct::Coproduct<$first, Coproduct!($($repeated), *)> + }; + + // <-- Forward trailing comma variants + ($single: ty,) => { + Coproduct![$single] + }; + + ($first: ty, $( $repeated: ty, ) +) => { + Coproduct![$first, $($repeated),*] + }; + // Forward trailing comma variants --> +} + +// <-- For turning something into a Coproduct +pub trait IntoCoproduct { + fn into(to_insert: InsertType) -> Self; +} + +impl IntoCoproduct for Coproduct { + fn into(to_insert: I) -> Self { + Coproduct::Inl(to_insert) + } +} + +impl IntoCoproduct> for Coproduct + where Tail: IntoCoproduct +{ + fn into(to_insert: I) -> Self { + let tail_inserted = >::into(to_insert); + Coproduct::Inr(tail_inserted) + } +} + +/// Function for returning a Coproduct from a given type +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { +/// type I32Bool = Coproduct!(i32, f32); +/// let co1: I32Bool = into_coproduct(42f32); +/// let get_from_1a: Option<&i32> = co1.get(); +/// let get_from_1b: Option<&f32> = co1.get(); +/// assert_eq!(get_from_1a, None); +/// assert_eq!(get_from_1b, Some(&42f32)); +/// # } +/// ``` +pub fn into_coproduct(to_into: I) -> C + where C: IntoCoproduct +{ + >::into(to_into) +} +// For turning something into a Coproduct --> + +/// Trait for retrieving a coproduct element by type +pub trait CoproductSelector { + fn get(&self) -> Option<&S>; +} + +impl CoproductSelector for Coproduct { + fn get(&self) -> Option<&Head> { + use self::Coproduct::*; + match *self { + Inl(ref thing) => Some(thing), + _ => None, // Impossible + } + } +} + +impl CoproductSelector> + for Coproduct + where Tail: CoproductSelector +{ + fn get(&self) -> Option<&FromTail> { + use self::Coproduct::*; + match *self { + Inr(ref rest) => rest.get(), + _ => None, // Impossible + } + } +} + +/// Trait for implementing "folding" a Coproduct into a value. +/// +/// The Folder should be an HList of closures that correspond (in order, for now..) to the +/// types used in declaring the Coproduct type. +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate frunk; +/// # use frunk::coproduct::*; +/// # use frunk::hlist::*; fn main() { +/// type I32StrBool = Coproduct!(i32, f32, bool); +/// +/// let co1: I32StrBool = into_coproduct(3); +/// let co2: I32StrBool = into_coproduct(true); +/// let co3: I32StrBool = into_coproduct(42f32); +/// +/// let folder = hlist![|i| format!("int {}", i), +/// |f| format!("float {}", f), +/// |b| (if b { "t" } else { "f" }).to_string()]; +/// +/// assert_eq!(co1.fold(&folder), "int 3".to_string()); +/// assert_eq!(co2.fold(&folder), "t".to_string()); +/// assert_eq!(co3.fold(&folder), "float 42".to_string()); +/// # } +/// ``` +pub trait CoproductFoldable { + fn fold(self, f: &Folder) -> Output; +} + +impl CoproductFoldable, R> for Coproduct + where F: Fn(CH) -> R, + CTail: CoproductFoldable +{ + fn fold(self, f: &HCons) -> R { + use self::Coproduct::*; + let ref f_head = f.head; + let ref f_tail = f.tail; + match self { + Inl(r) => (f_head)(r), + Inr(rest) => rest.fold(f_tail), + } + } +} + +/// This is literally impossible; CNil is not instantiable +#[doc(hidden)] +impl CoproductFoldable for CNil { + fn fold(self, _: &F) -> R { + unreachable!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::Coproduct::*; + + #[test] + fn test_into_coproduct() { + type I32StrBool = Coproduct!(i32, &'static str, bool); + + let co1: I32StrBool = into_coproduct(3); + assert_eq!(co1, Inl(3)); + let get_from_1a: Option<&i32> = co1.get(); + let get_from_1b: Option<&bool> = co1.get(); + assert_eq!(get_from_1a, Some(&3)); + assert_eq!(get_from_1b, None); + + + let co2: I32StrBool = into_coproduct(false); + assert_eq!(co2, Inr(Inr(Inl(false)))); + let get_from_2a: Option<&i32> = co2.get(); + let get_from_2b: Option<&bool> = co2.get(); + assert_eq!(get_from_2a, None); + assert_eq!(get_from_2b, Some(&false)); + } + + #[test] + fn test_coproduct_fold() { + type I32StrBool = Coproduct!(i32, f32, bool); + + let co1: I32StrBool = into_coproduct(3); + let co2: I32StrBool = into_coproduct(true); + let co3: I32StrBool = into_coproduct(42f32); + + let folder = hlist![|i| format!("int {}", i), + |f| format!("float {}", f), + |b| (if b { "t" } else { "f" }).to_string()]; + + assert_eq!(co1.fold(&folder), "int 3".to_string()); + assert_eq!(co2.fold(&folder), "t".to_string()); + assert_eq!(co3.fold(&folder), "float 42".to_string()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 969b85037..2af06f8e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,10 +111,7 @@ extern crate frunk_derives; pub mod semigroup; pub mod monoid; pub mod validated; +pub mod coproduct; -pub use frunk_core::hlist::*; -pub use frunk_core::labelled::*; -// this needs to be globally imported in order for custom derives to work w/o fuss -pub use frunk_core::generic::*; - +pub use frunk_core::*; pub use frunk_derives::*; diff --git a/tests/generic_tests.rs b/tests/generic_tests.rs index eba1551c9..cadd7f472 100644 --- a/tests/generic_tests.rs +++ b/tests/generic_tests.rs @@ -2,7 +2,8 @@ extern crate frunk; #[macro_use] // for the hlist macro extern crate frunk_core; -use frunk::*; // for the Generic trait and HList +use frunk::hlist::*; +use frunk::generic::*; mod common; use common::*; diff --git a/tests/labelled_tests.rs b/tests/labelled_tests.rs index 5150a11a9..7005ff6d5 100644 --- a/tests/labelled_tests.rs +++ b/tests/labelled_tests.rs @@ -3,7 +3,8 @@ extern crate frunk; extern crate frunk_core; extern crate time; //Time library -use frunk::*; // for the Generic trait and HList +use frunk::hlist::*; +use frunk::labelled::*; mod common; diff --git a/tests/validated_tests.rs b/tests/validated_tests.rs index 2f23f7bd1..891f8ad9f 100644 --- a/tests/validated_tests.rs +++ b/tests/validated_tests.rs @@ -1,7 +1,7 @@ extern crate frunk; extern crate frunk_core; -use frunk::*; // for the Generic trait and HList +use frunk::generic::*; // for the Generic trait and HList use frunk::validated::*; mod common; From 698e0924e9649750e55391c34b643f4ebd35352f Mon Sep 17 00:00:00 2001 From: Lloyd Date: Wed, 19 Apr 2017 02:37:29 +0900 Subject: [PATCH 3/8] 0.1.23 release --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 539d8b242..307677fb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "frunk" -version = "0.1.22" +version = "0.1.23" authors = ["Lloyd "] -description = "Frunk provides developers with a number of functional programming tools like HList, Generic, LabelledGeneric, Validated, Monoid, Semigroup and friends." +description = "Frunk provides developers with a number of functional programming tools like HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid, Semigroup and friends." license = "MIT" documentation = "https://docs.rs/frunk" repository = "https://github.com/lloydmeta/frunk" From 6394a7701a6bc5e86e1260b62565edaf9fd1ca4c Mon Sep 17 00:00:00 2001 From: Lloyd Date: Wed, 19 Apr 2017 15:09:23 +0900 Subject: [PATCH 4/8] Add big transform_from benchmarks --- benches/generic.rs | 16 +-- benches/labelled.rs | 279 +++++++++++++++++++++++++++++++++++++++++++- benches/monoid.rs | 3 +- 3 files changed, 287 insertions(+), 11 deletions(-) diff --git a/benches/generic.rs b/benches/generic.rs index 72b91d3de..1f442ec84 100644 --- a/benches/generic.rs +++ b/benches/generic.rs @@ -5,7 +5,7 @@ extern crate frunk; extern crate frunk_core; extern crate test; -use frunk::*; +use frunk::generic::*; use test::Bencher; #[derive(Generic)] @@ -25,11 +25,11 @@ struct SavedUser<'a> { #[bench] fn generic_conversion(b: &mut Bencher) { b.iter(|| { - let n_u = NewUser { - first_name: "Joe", - last_name: "Schmoe", - age: 30, - }; - ::convert_from(n_u) - }) + let n_u = NewUser { + first_name: "Joe", + last_name: "Schmoe", + age: 30, + }; + SavedUser::convert_from(n_u) + }) } diff --git a/benches/labelled.rs b/benches/labelled.rs index 3a8d9248d..845a2f0b2 100644 --- a/benches/labelled.rs +++ b/benches/labelled.rs @@ -6,8 +6,9 @@ extern crate frunk; extern crate frunk_core; extern crate test; -use frunk::*; +use frunk::labelled::*; use test::Bencher; +use std::convert::From; #[derive(LabelledGeneric)] struct NewUser<'a> { @@ -22,6 +23,7 @@ struct SavedUser<'a> { last_name: &'a str, age: usize, } + #[derive(LabelledGeneric)] struct JumbledUser<'a> { age: usize, @@ -29,6 +31,242 @@ struct JumbledUser<'a> { first_name: &'a str, } +#[derive(LabelledGeneric)] +struct BigStruct24Fields { + a: i8, + b: i32, + c: f64, + d: usize, + e: String, + f: i8, + g: i32, + h: f64, + i: usize, + j: String, + k: i8, + l: i32, + m: f64, + n: usize, + o: String, + p: i8, + q: i32, + r: f64, + s: usize, + t: String, + u: i8, + v: i32, + w: f64, + x: usize +} + +#[derive(LabelledGeneric)] +struct BigStruct25Fields { + a: i8, + b: i32, + c: f64, + d: usize, + e: String, + f: i8, + g: i32, + h: f64, + i: usize, + j: String, + k: i8, + l: i32, + m: f64, + n: usize, + o: String, + p: i8, + q: i32, + r: f64, + s: usize, + t: String, + u: i8, + v: i32, + w: f64, + x: usize, + y: String +} + +#[derive(LabelledGeneric)] +struct BigStruct24FieldsReverse { + x: usize, + w: f64, + v: i32, + u: i8, + t: String, + s: usize, + r: f64, + q: i32, + p: i8, + o: String, + n: usize, + m: f64, + l: i32, + k: i8, + j: String, + i: usize, + h: f64, + g: i32, + f: i8, + e: String, + d: usize, + c: f64, + b: i32, + a: i8, +} + +#[derive(LabelledGeneric)] +struct BigStruct25FieldsReverse { + y: String, + x: usize, + w: f64, + v: i32, + u: i8, + t: String, + s: usize, + r: f64, + q: i32, + p: i8, + o: String, + n: usize, + m: f64, + l: i32, + k: i8, + j: String, + i: usize, + h: f64, + g: i32, + f: i8, + e: String, + d: usize, + c: f64, + b: i32, + a: i8, +} + +impl From for BigStruct24FieldsReverse { + fn from(b: BigStruct24Fields) -> Self { + BigStruct24FieldsReverse { + a: b.a, + b: b.b, + c: b.c, + d: b.d, + e: b.e, + f: b.f, + g: b.g, + h: b.h, + i: b.i, + j: b.j, + k: b.k, + l: b.l, + m: b.m, + n: b.n, + o: b.o, + p: b.p, + q: b.q, + r: b.r, + s: b.s, + t: b.t, + u: b.u, + v: b.v, + w: b.w, + x: b.x, + } + } +} + +impl From for BigStruct25FieldsReverse { + fn from(b: BigStruct25Fields) -> Self { + BigStruct25FieldsReverse { + a: b.a, + b: b.b, + c: b.c, + d: b.d, + e: b.e, + f: b.f, + g: b.g, + h: b.h, + i: b.i, + j: b.j, + k: b.k, + l: b.l, + m: b.m, + n: b.n, + o: b.o, + p: b.p, + q: b.q, + r: b.r, + s: b.s, + t: b.t, + u: b.u, + v: b.v, + w: b.w, + x: b.x, + y: b.y + } + } +} + +fn build_big_struct_24fields() -> BigStruct24Fields { + BigStruct24Fields { + a: 10, + b: 100, + c: 42f64, + d: 9001, + e: "Hello World".to_string(), + f: 10, + g: 100, + h: 42f64, + i: 9001, + j: "Hello World".to_string(), + k: 10, + l: 100, + m: 42f64, + n: 9001, + o: "Hello World".to_string(), + p: 10, + q: 100, + r: 42f64, + s: 9001, + t: "Hello World".to_string(), + u: 10, + v: 100, + w: 42f64, + x: 9001, + } +} + +fn build_big_struct_25fields() -> BigStruct25Fields { + BigStruct25Fields { + a: 10, + b: 100, + c: 42f64, + d: 9001, + e: "Hello World".to_string(), + f: 10, + g: 100, + h: 42f64, + i: 9001, + j: "Hello World".to_string(), + k: 10, + l: 100, + m: 42f64, + n: 9001, + o: "Hello World".to_string(), + p: 10, + q: 100, + r: 42f64, + s: 9001, + t: "Hello World".to_string(), + u: 10, + v: 100, + w: 42f64, + x: 9001, + y: "Hello World".to_string(), + } +} + #[bench] fn labelled_conversion(b: &mut Bencher) { b.iter(|| { @@ -40,6 +278,7 @@ fn labelled_conversion(b: &mut Bencher) { ::convert_from(n_u) }) } + #[bench] fn sculpted_conversion(b: &mut Bencher) { b.iter(|| { @@ -48,10 +287,46 @@ fn sculpted_conversion(b: &mut Bencher) { last_name: "Schmoe", age: 30, }; - ::transform_from(n_u) + JumbledUser::transform_from(n_u) + }) +} + +#[bench] +fn big_transform_from_24fields(b: &mut Bencher) { + b.iter(|| { + let j = BigStruct24FieldsReverse::transform_from(build_big_struct_24fields()); + j }) } +#[bench] +fn big_from_24fields(b: &mut Bencher) { + b.iter(|| { + let j = >::from(build_big_struct_24fields()); + j + }) +} + +// Hilariously, uncommenting this out will kill the performance in the above 2 benchmarks + +//#[bench] +//fn big_transform_from_25fields(b: &mut Bencher) { +// b.iter(|| { +// let j = BigStruct25FieldsReverse::transform_from(build_big_struct_25fields()); +// j +// }) +//} +// +//#[bench] +//fn big_from_25fields(b: &mut Bencher) { +// b.iter(|| { +// let j = >::from(build_big_struct_25fields()); +// j +// }) +//} + + + #[bench] fn name(b: &mut Bencher) { let field = field!((f, i, r, s, t, __, n, a, m, e), "Joe"); diff --git a/benches/monoid.rs b/benches/monoid.rs index de6ea5c5d..3f0e01bf1 100644 --- a/benches/monoid.rs +++ b/benches/monoid.rs @@ -18,7 +18,8 @@ fn std_add_all_i32(b: &mut Bencher) { let v = vec![Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8), Some(9), Some(10)]; b.iter(|| { - v.iter().fold(Some(0), |maybe_acc, maybe_n| { + v.iter() + .fold(Some(0), |maybe_acc, maybe_n| { maybe_acc.and_then(|acc| maybe_n.map(|n| acc + n)) }) }) From 275c490d592e89f0e16d03d8ea1330315996033d Mon Sep 17 00:00:00 2001 From: Lloyd Date: Wed, 19 Apr 2017 22:41:31 +0900 Subject: [PATCH 5/8] Feature/non consuming transforms (#48) * Add non-consuming Coproduct fold, HList map and foldLeft * Missing HList foldRight because of https://github.com/rust-lang/rust/issues/39959 --- core/src/hlist.rs | 254 +++++++++++++++++++++++++++++++++++-------- src/coproduct.rs | 96 ++++++++++++---- src/lib.rs | 6 +- travis-after-success | 4 +- 4 files changed, 288 insertions(+), 72 deletions(-) diff --git a/core/src/hlist.rs b/core/src/hlist.rs index 841333293..aada5c438 100644 --- a/core/src/hlist.rs +++ b/core/src/hlist.rs @@ -29,12 +29,13 @@ //! ); //! assert_eq!(folded, 9001); //! -//! // Mapping over an HList //! let h3 = hlist![9000, "joe", 41f32]; -//! let mapped = h3.map(hlist![ -//! |n| n + 1, -//! |s| s, -//! |f| f + 1f32]); +//! // Mapping over an HList (we use as_ref() to map over the HList without consuming it, +//! // but you can use the value-consuming version by leaving it off.) +//! let mapped = h3.as_ref().map(hlist![ +//! |&n| n + 1, +//! |&s| s, +//! |&f| f + 1f32]); //! assert_eq!(mapped, hlist![9001, "joe", 42f32]); //! //! // Plucking a value out by type @@ -112,6 +113,10 @@ impl HList for HNil { } } +impl AsRef for HNil { + fn as_ref(&self) -> &HNil { self } +} + /// Represents the most basic non-empty HList. Its value is held in `head` /// while its tail is another HList. #[derive(PartialEq, Debug, Eq, Clone, Copy, PartialOrd, Ord)] @@ -126,6 +131,10 @@ impl HList for HCons { } } +impl AsRef> for HCons { + fn as_ref(&self) -> &HCons { self } +} + impl HCons { /// Returns the head of the list and the tail of the list as a tuple2. /// The original list is consumed @@ -160,7 +169,7 @@ impl HCons { /// assert_eq!(h2, 1.23f32); /// ``` pub fn h_cons(h: H, tail: T) -> HCons { - tail.prepend(h) + HCons { head: h, tail: tail } } /// Returns an `HList` based on the values passed in. @@ -440,8 +449,8 @@ impl Sculptor for Source { /// Index for Plucking the first item of type THead out of Self and the rest (IndexTail) is for the /// Plucker's remainder induce. impl Sculptor, - HCons> - for HCons + HCons> +for HCons where HCons: Plucker, as Plucker>::Remainder: Sculptor { @@ -452,9 +461,9 @@ impl Sculptor IntoReverse for HCons fn into_reverse(self) -> Self::Output { self.tail.into_reverse() + - HCons { - head: self.head, - tail: HNil, - } + HCons { + head: self.head, + tail: HNil, + } } } @@ -529,11 +538,20 @@ pub trait HMappable { /// /// // Sadly we need to help the compiler understand the bool type in our mapper /// - /// let mapped = h.map(hlist![ - /// |n| n + 1, - /// |b: bool| !b, - /// |f| f + 1f32]); + /// let mapped = h.as_ref().map(hlist![ + /// |&n| n + 1, + /// |b: &bool| !b, + /// |&f| f + 1f32]); /// assert_eq!(mapped, hlist![2, true, 43f32]); + /// + /// // There is also a value-consuming version that passes values to your functions + /// // instead of just references: + /// + /// let mapped2 = h.map(hlist![ + /// |n| n + 3, + /// |b: bool| !b, + /// |f| f + 8959f32]); + /// assert_eq!(mapped2, hlist![4, true, 9001f32]); /// # } /// ``` fn map(self, folder: Mapper) -> Self::Output; @@ -547,6 +565,17 @@ impl HMappable for HNil { } } +impl<'a, F, R, H> HMappable> for &'a HCons + where F: FnOnce(&'a H) -> R { + type Output = HCons; + + fn map(self, f: HCons) -> Self::Output { + let ref h = self.head; + let f = f.head; + HCons { head: f(h), tail: HNil } + } +} + impl HMappable> for HCons where F: FnOnce(H) -> MapperHeadR, Tail: HMappable @@ -561,12 +590,33 @@ impl HMappable> for HC } } +// TODO take a mapper by reference when https://github.com/rust-lang/rust/issues/39959 is fixed +impl<'a, F, MapperHeadR, MapperTail, H, Tail> HMappable> for &'a HCons + where F: FnOnce(&'a H) -> MapperHeadR, + &'a Tail: HMappable +{ + type Output = HCons>::Output>; + fn map(self, mapper: HCons) -> Self::Output { + let f = mapper.head; + let mapper_tail = mapper.tail; + let ref self_head = self.head; + let ref self_tail = self.tail; + HCons { + head: f(self_head), + tail: self_tail.map(mapper_tail), + } + } +} + /// Foldr for HLists -pub trait HFoldRightable { +pub trait HFoldRightable { type Output; /// foldr over a data structure /// + /// Sadly, due to a compiler quirk, only the value-consuming (the original hlist) variant + /// exists for foldr. + /// /// # Examples /// /// ``` @@ -593,7 +643,7 @@ pub trait HFoldRightable { fn foldr(self, folder: Folder, i: Init) -> Self::Output; } -impl HFoldRightable for HNil { +impl HFoldRightable for HNil { type Output = Init; fn foldr(self, _: F, i: Init) -> Self::Output { @@ -601,10 +651,10 @@ impl HFoldRightable for HNil { } } -impl HFoldRightable, Init> - for HCons - where Tail: HFoldRightable, - F: FnOnce(H, >::Output) -> FolderHeadR + +impl HFoldRightable, Init, There> for HCons + where Tail: HFoldRightable, + F: FnOnce(H, >::Output) -> FolderHeadR { type Output = FolderHeadR; @@ -614,8 +664,47 @@ impl HFoldRightable>::Output` +// +// or +// +// overflow evaluating the requirement `<&_ as hlist::HFoldRightable<_, _, _>>::Output` +// +// Depending on the exit-case implementation +// +//impl<'a, F, Init> HFoldRightable for &'a HNil { +// type Output = Init; +// +// fn foldr(self, _: F, i: Init) -> Self::Output { +// i +// } +//} +// +// +//impl<'a, F, FolderHeadR, FolderTail, H, Tail, Init, Index> HFoldRightable, Init, There> for &'a HCons +// where +// F: Fn(&'a H, <&'a Tail as HFoldRightable>::Output) -> FolderHeadR, +// &'a Tail: HFoldRightable +// +//{ +// type Output = FolderHeadR; +// +// fn foldr(self, folder: HCons, init: Init) -> Self::Output { +// let f_h = folder.head; +// let f_tail = folder.tail; +// let ref h = self.head; +// let ref tail = self.tail; +// let folded_tail = tail.foldr(f_tail, init); +// (f_h)(h, folded_tail) +// } +//} + /// Left fold for a given data structure -pub trait HFoldLeftable { +pub trait HFoldLeftable { type Output; /// foldl over a data structure @@ -630,23 +719,36 @@ pub trait HFoldLeftable { /// /// let h = hlist![1, false, 42f32]; /// - /// let folded = h.foldl( + /// let folded = h.as_ref().foldl( + /// hlist![ + /// |acc, &i| i + acc, + /// |acc, b: &bool| if !b && acc > 42 { 9000f32 } else { 0f32 }, + /// |acc, &f| f + acc + /// ], + /// 1 + /// ); + /// + /// assert_eq!(42f32, folded); + /// + /// // There is also a value-consuming version that passes values to your folding + /// // functions instead of just references: + /// + /// let folded2 = h.foldl( /// hlist![ /// |acc, i| i + acc, /// |acc, b: bool| if !b && acc > 42 { 9000f32 } else { 0f32 }, /// |acc, f| f + acc /// ], - /// 1 + /// 8918 /// ); /// - /// assert_eq!(42f32, folded) - /// + /// assert_eq!(9042f32, folded2) /// # } /// ``` fn foldl(self, folder: Folder, i: Init) -> Self::Output; } -impl HFoldLeftable for HNil { +impl HFoldLeftable for HNil { type Output = Acc; fn foldl(self, _: F, acc: Acc) -> Self::Output { @@ -654,12 +756,23 @@ impl HFoldLeftable for HNil { } } -impl HFoldLeftable, Acc> - for HCons - where Tail: HFoldLeftable, +impl<'a, F, R, H, Acc> HFoldLeftable, Acc, Here> for &'a HCons where + F: FnOnce(Acc, &'a H) -> R { + type Output = R; + + fn foldl(self, folder: HCons, acc: Acc) -> Self::Output { + let f = folder.head; + let ref h = self.head; + f(acc, h) + } +} + +impl HFoldLeftable, Acc, There> +for HCons + where Tail: HFoldLeftable, F: FnOnce(Acc, H) -> FolderHeadR { - type Output = >::Output; + type Output = >::Output; fn foldl(self, folder: HCons, acc: Acc) -> Self::Output { self.tail @@ -667,6 +780,22 @@ impl HFoldLeftable HFoldLeftable, Acc, There> +for &'a HCons + where &'a Tail: HFoldLeftable, + F: FnOnce(Acc, &'a H) -> FolderHeadR +{ + type Output = <&'a Tail as HFoldLeftable>::Output; + + fn foldl(self, folder: HCons, acc: Acc) -> Self::Output { + let ref h = self.head; + let ref t = self.tail; + let f_head = folder.head; + let f_tail = folder.tail; + t.foldl(f_tail, (f_head)(acc, h)) + } +} + /// Trait for things that can be turned into a Tuple 2 (pair) pub trait IntoTuple2 { /// The 0 element in the output tuple @@ -772,10 +901,14 @@ mod tests { #[test] #[allow(non_snake_case)] fn test_Hlist_macro() { - let h1: Hlist!(i32, &str, i32) = hlist![1, "2", 3]; - let h2: Hlist!(i32, &str, i32,) = hlist![1, "2", 3]; - let h3: Hlist!(i32) = hlist![1]; - let h4: Hlist!(i32,) = hlist![1,]; + let h1: Hlist + !(i32, &str, i32) = hlist![1, "2", 3]; + let h2: Hlist + !(i32, &str, i32, ) = hlist![1, "2", 3]; + let h3: Hlist + !(i32) = hlist![1]; + let h4: Hlist + !(i32, ) = hlist![1,]; assert_eq!(h1, h2); assert_eq!(h3, h4); } @@ -818,7 +951,7 @@ mod tests { } #[test] - fn test_foldr() { + fn test_foldr_consuming() { let h = hlist![1, false, 42f32]; let folded = h.foldr(hlist![|i, acc| i + acc, |_, acc| if acc > 42f32 { 9000 } else { 0 }, @@ -827,8 +960,20 @@ mod tests { assert_eq!(folded, 9001) } + // Todo enable when compiler is fixed +// #[test] +// fn test_foldr_non_consuming() { +// let h = hlist![1, false, 42f32]; +// let folder = hlist![|&i, acc| i + acc, +// |&_, acc| if acc > 42f32 { 9000 } else { 0 }, +// |&f, acc| f + acc]; +// let folded = h.as_ref().foldr(folder, +// 1f32); +// assert_eq!(folded, 9001) +// } + #[test] - fn test_foldl() { + fn test_foldl_consuming() { let h = hlist![1, false, 42f32]; let folded = h.foldl(hlist![|acc, i| i + acc, |acc, b: bool| if !b && acc > 42 { 9000f32 } else { 0f32 }, @@ -838,9 +983,32 @@ mod tests { } #[test] - fn test_map() { + fn test_foldl_non_consuming() { + let h = hlist![1, false, 42f32]; + let folded = h.as_ref().foldl(hlist![|acc, &i| i + acc, + |acc, b: &bool| if !b && acc > 42 { 9000f32 } else { 0f32 }, + |acc, &f| f + acc], + 1); + assert_eq!(42f32, folded) + } + + #[test] + fn test_map_consuming() { + let h = hlist![9000, "joe", 41f32]; + let mapped = h.map(hlist![ + |n| n + 1, + |s| s, + |f| f + 1f32]); + assert_eq!(mapped, hlist![9001, "joe", 42f32]); + } + + #[test] + fn test_map_non_consuming() { let h = hlist![9000, "joe", 41f32]; - let mapped = h.map(hlist![|n| n + 1, |s| s, |f| f + 1f32]); + let mapped = h.as_ref().map(hlist![ + |&n| n + 1, + |&s| s, + |&f| f + 1f32]); assert_eq!(mapped, hlist![9001, "joe", 42f32]); } diff --git a/src/coproduct.rs b/src/coproduct.rs index a331171b1..a9163d0d3 100644 --- a/src/coproduct.rs +++ b/src/coproduct.rs @@ -31,14 +31,21 @@ //! # type I32Bool = Coproduct!(i32, bool); //! # let co1: I32Bool = into_coproduct(3); //! # let co2: I32Bool = into_coproduct(true); -//! //! let folder = hlist![ -//! |i| format!("i32 {}", i), -//! |b| String::from(if b { "t" } else { "f" }) +//! |&i| format!("i32 {}", i), +//! |&b| String::from(if b { "t" } else { "f" }) //! ]; //! -//! assert_eq!(co1.fold(&folder), "i32 3".to_string()); -//! assert_eq!(co2.fold(&folder), "t".to_string()); +//! assert_eq!(co1.as_ref().fold(&folder), "i32 3".to_string()); +//! assert_eq!(co2.as_ref().fold(&folder), "t".to_string()); +//! +//! // There is also a value consuming-variant of fold +//! +//! let folded = co1.fold(hlist![ +//! |i| format!("i32 {}", i), +//! |b| String::from(if b { "t" } else { "f" }) +//! ]); +//! assert_eq!(folded, "i32 3".to_string()); //! # } //! ``` @@ -200,27 +207,27 @@ impl CoproductSelector { - fn fold(self, f: &Folder) -> Output; + fn fold(self, f: Folder) -> Output; } impl CoproductFoldable, R> for Coproduct - where F: Fn(CH) -> R, + where F: FnOnce(CH) -> R, CTail: CoproductFoldable { - fn fold(self, f: &HCons) -> R { + fn fold(self, f: HCons) -> R { use self::Coproduct::*; - let ref f_head = f.head; - let ref f_tail = f.tail; + let f_head = f.head; + let f_tail = f.tail; match self { Inl(r) => (f_head)(r), Inr(rest) => rest.fold(f_tail), @@ -228,14 +235,43 @@ impl CoproductFoldable, R> for Coproduct } } +impl<'a, F, R, FTail, CH, CTail> CoproductFoldable<&'a HCons, R> for &'a Coproduct + where F: Fn(&'a CH) -> R, + &'a CTail: CoproductFoldable<&'a FTail, R> +{ + fn fold(self, f: &'a HCons) -> R { + use self::Coproduct::*; + let ref f_head = f.head; + let ref f_tail = f.tail; + match *self { + Inl(ref r) => (f_head)(r), + Inr(ref rest) => <&'a CTail as CoproductFoldable<&'a FTail, R>>::fold(rest, f_tail), + } + } +} + /// This is literally impossible; CNil is not instantiable #[doc(hidden)] impl CoproductFoldable for CNil { - fn fold(self, _: &F) -> R { + fn fold(self, _: F) -> R { + unreachable!() + } +} + +/// This is literally impossible; &CNil is not instantiable +#[doc(hidden)] +impl<'a, F, R> CoproductFoldable<&'a F, R> for &'a CNil { + fn fold(self, _: &'a F) -> R { unreachable!() } } +impl AsRef> for Coproduct { + fn as_ref(&self) -> &Coproduct { + self + } +} + #[cfg(test)] mod tests { use super::*; @@ -262,19 +298,31 @@ mod tests { } #[test] - fn test_coproduct_fold() { + fn test_coproduct_fold_consuming() { + type I32StrBool = Coproduct!(i32, f32, bool); + + let co1: I32StrBool = into_coproduct(3); + let folded = co1.fold(hlist![|i| format!("int {}", i), + |f| format!("float {}", f), + |b| (if b { "t" } else { "f" }).to_string()]); + + assert_eq!(folded, "int 3".to_string()); + } + + #[test] + fn test_coproduct_fold_non_consuming() { type I32StrBool = Coproduct!(i32, f32, bool); let co1: I32StrBool = into_coproduct(3); let co2: I32StrBool = into_coproduct(true); let co3: I32StrBool = into_coproduct(42f32); - let folder = hlist![|i| format!("int {}", i), - |f| format!("float {}", f), - |b| (if b { "t" } else { "f" }).to_string()]; + let folder = hlist![|&i| format!("int {}", i), + |&f| format!("float {}", f), + |&b| (if b { "t" } else { "f" }).to_string()]; - assert_eq!(co1.fold(&folder), "int 3".to_string()); - assert_eq!(co2.fold(&folder), "t".to_string()); - assert_eq!(co3.fold(&folder), "float 42".to_string()); + assert_eq!(co1.as_ref().fold(&folder), "int 3".to_string()); + assert_eq!(co2.as_ref().fold(&folder), "t".to_string()); + assert_eq!(co3.as_ref().fold(&folder), "float 42".to_string()); } } diff --git a/src/lib.rs b/src/lib.rs index 2af06f8e0..ee9e77ec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,9 +15,9 @@ //! # #[macro_use] extern crate frunk; //! # #[macro_use] extern crate frunk_core; //! # use frunk_core::hlist::*; fn main() { -//! use frunk_core::hlist::*; -//! use frunk_core::generic::*; -//! use frunk_core::labelled::*; +//! use frunk::hlist::*; +//! use frunk::generic::*; +//! use frunk::labelled::*; //! use frunk::monoid::*; //! use frunk::semigroup::*; //! use frunk::validated::*; diff --git a/travis-after-success b/travis-after-success index 1f8247046..bf5fa0695 100755 --- a/travis-after-success +++ b/travis-after-success @@ -9,9 +9,9 @@ if [ "${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}" != "master" ] && [ "$TRAVIS cd "${TRAVIS_REPO_SLUG}-bench" && \ # Bench master git checkout master && \ - cargo bench > benches-control && \ + cargo bench > "${TRAVIS_BUILD_DIR}/benches-control" && \ # Bench variable - git checkout ${TRAVIS_COMMIT} && \ + cd ${TRAVIS_BUILD_DIR} && \ cargo bench > benches-variable && \ cargo install cargo-benchcmp --force && \ cargo benchcmp benches-control benches-variable; From f6b2bb24eeb0531a8fe26c25c11000f58fb4c1cc Mon Sep 17 00:00:00 2001 From: Lloyd Date: Wed, 19 Apr 2017 22:47:07 +0900 Subject: [PATCH 6/8] Update versions for release --- Cargo.toml | 8 ++++---- core/Cargo.toml | 4 ++-- derives/Cargo.toml | 4 ++-- laws/Cargo.toml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 307677fb6..e4faf44af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk" -version = "0.1.23" +version = "0.1.24" authors = ["Lloyd "] description = "Frunk provides developers with a number of functional programming tools like HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid, Semigroup and friends." license = "MIT" @@ -16,12 +16,12 @@ time = "0.1.36" [dependencies.frunk_core] path = "core" -version = "0.0.11" +version = "0.0.12" [dependencies.frunk_derives] path = "derives" -version = "0.0.12" +version = "0.0.13" [dev-dependencies.frunk_laws] path = "laws" -version = "0.0.1" \ No newline at end of file +version = "0.0.2" \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index 103acdb49..d9d9df4cd 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk_core" -version = "0.0.11" +version = "0.0.12" authors = ["Lloyd "] description = "Frunk core provides developers with HList and Generic" license = "MIT" @@ -13,4 +13,4 @@ travis-ci = { repository = "lloydmeta/frunk" } [dev-dependencies.frunk_derives] path = "../derives" -version = "0.0.12" +version = "0.0.13" diff --git a/derives/Cargo.toml b/derives/Cargo.toml index 8a8c4e196..621a189bc 100644 --- a/derives/Cargo.toml +++ b/derives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk_derives" -version = "0.0.12" +version = "0.0.13" authors = ["Lloyd "] description = "frunk_derives contains the custom derivations for certain traits in Frunk." license = "MIT" @@ -20,4 +20,4 @@ quote = "0.3.15" [dependencies.frunk_core] path = "../core" -version = "0.0.11" +version = "0.0.12" diff --git a/laws/Cargo.toml b/laws/Cargo.toml index de62b3dcb..6619ab291 100644 --- a/laws/Cargo.toml +++ b/laws/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk_laws" -version = "0.0.1" +version = "0.0.2" authors = ["Lloyd "] description = "frunk_laws contains laws for algebras declared in Frunk." license = "MIT" @@ -13,7 +13,7 @@ travis-ci = { repository = "lloydmeta/frunk" } [dependencies.frunk] path = ".." -version = "0.1.12" +version = "0.1.13" [dependencies] From e6a32170e893864d7b3ba29535b10149c49616da Mon Sep 17 00:00:00 2001 From: Lloyd Date: Thu, 20 Apr 2017 13:36:21 +0900 Subject: [PATCH 7/8] Feature/coproduct take (#49) - Rename Coproduct! type macro to Coprod! - Make coproduct folding take an hlist of functions by value so we can ease the restraint to FnOnce from Fn - Fix unnamespaced references to CNil and HNil in macros --- README.md | 13 +-- benches/labelled.rs | 40 +++++----- core/src/hlist.rs | 19 ++--- core/src/lib.rs | 15 ++-- src/coproduct.rs | 167 ++++++++++++++++++++++++++++----------- src/monoid.rs | 3 +- src/semigroup.rs | 4 +- src/validated.rs | 18 ++--- tests/coproduct_tests.rs | 41 ++++++++++ tests/generic_tests.rs | 1 - 10 files changed, 215 insertions(+), 106 deletions(-) create mode 100644 tests/coproduct_tests.rs diff --git a/README.md b/README.md index 653baadb7..5a5266fe4 100644 --- a/README.md +++ b/README.md @@ -279,11 +279,11 @@ to take a look at `Coproduct`. In Rust, thanks to `enum`, you could potentially want a sum type to do this, but there is a light-weight way of doing it through Frunk: ```rust -#[macro_use] extern crate frunk; // for the Coproduct! type macro +#[macro_use] extern crate frunk; // for the Coprod! type macro use frunk::coproduct::*; // Declare the types we want in our Coproduct -type I32Bool = Coproduct!(i32, f32, bool); +type I32Bool = Coprod!(i32, f32, bool); let co1: I32Bool = into_coproduct(3); let get_from_1a: Option<&i32> = co1.get(); @@ -295,11 +295,12 @@ 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); -// We can fold our Coproduct into a single value by handling all cases -let folder = hlist![|i| format!("int {}", i), +// 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()]; -assert_eq!(co1.fold(&folder), "int 3".to_string()); + |b| (if b { "t" } else { "f" }).to_string()]), + "int 3".to_string()); ``` For more information, check out the []docs for Coproduct](https://beachape.com/frunk/frunk/coproduct/index.html) diff --git a/benches/labelled.rs b/benches/labelled.rs index 845a2f0b2..244eff270 100644 --- a/benches/labelled.rs +++ b/benches/labelled.rs @@ -56,7 +56,7 @@ struct BigStruct24Fields { u: i8, v: i32, w: f64, - x: usize + x: usize, } #[derive(LabelledGeneric)] @@ -85,7 +85,7 @@ struct BigStruct25Fields { v: i32, w: f64, x: usize, - y: String + y: String, } #[derive(LabelledGeneric)] @@ -203,7 +203,7 @@ impl From for BigStruct25FieldsReverse { v: b.v, w: b.w, x: b.x, - y: b.y + y: b.y, } } } @@ -270,33 +270,33 @@ fn build_big_struct_25fields() -> BigStruct25Fields { #[bench] fn labelled_conversion(b: &mut Bencher) { b.iter(|| { - let n_u = NewUser { - first_name: "Joe", - last_name: "Schmoe", - age: 30, - }; - ::convert_from(n_u) - }) + let n_u = NewUser { + first_name: "Joe", + last_name: "Schmoe", + age: 30, + }; + ::convert_from(n_u) + }) } #[bench] fn sculpted_conversion(b: &mut Bencher) { b.iter(|| { - let n_u = NewUser { - first_name: "Joe", - last_name: "Schmoe", - age: 30, - }; - JumbledUser::transform_from(n_u) - }) + let n_u = NewUser { + first_name: "Joe", + last_name: "Schmoe", + age: 30, + }; + JumbledUser::transform_from(n_u) + }) } #[bench] fn big_transform_from_24fields(b: &mut Bencher) { b.iter(|| { - let j = BigStruct24FieldsReverse::transform_from(build_big_struct_24fields()); - j - }) + let j = BigStruct24FieldsReverse::transform_from(build_big_struct_24fields()); + j + }) } #[bench] diff --git a/core/src/hlist.rs b/core/src/hlist.rs index aada5c438..94b076f1e 100644 --- a/core/src/hlist.rs +++ b/core/src/hlist.rs @@ -20,11 +20,9 @@ //! // foldr (foldl also available) //! let h2 = hlist![1, false, 42f32]; //! let folded = h2.foldr( -//! hlist![ -//! |i, acc| i + acc, -//! |_, acc| if acc > 42f32 { 9000 } else { 0 }, -//! |f, acc| f + acc -//! ], +//! hlist![|i, acc| i + acc, +//! |_, acc| if acc > 42f32 { 9000 } else { 0 }, +//! |f, acc| f + acc], //! 1f32 //! ); //! assert_eq!(folded, 9001); @@ -32,10 +30,9 @@ //! let h3 = hlist![9000, "joe", 41f32]; //! // Mapping over an HList (we use as_ref() to map over the HList without consuming it, //! // but you can use the value-consuming version by leaving it off.) -//! let mapped = h3.as_ref().map(hlist![ -//! |&n| n + 1, -//! |&s| s, -//! |&f| f + 1f32]); +//! let mapped = h3.as_ref().map(hlist![|&n| n + 1, +//! |&s| s, +//! |&f| f + 1f32]); //! assert_eq!(mapped, hlist![9001, "joe", 42f32]); //! //! // Plucking a value out by type @@ -201,7 +198,7 @@ macro_rules! hlist { // Just a single item ($single: expr) => { - $crate::hlist::HCons { head: $single, tail: HNil } + $crate::hlist::HCons { head: $single, tail: $crate::hlist::HNil } }; ($first: expr, $( $repeated: expr ), +) => { @@ -266,7 +263,7 @@ macro_rules! Hlist { // Just a single item ($single: ty) => { - $crate::hlist::HCons<$single, HNil> + $crate::hlist::HCons<$single, $crate::hlist::HNil> }; ($first: ty, $( $repeated: ty ), +) => { diff --git a/core/src/lib.rs b/core/src/lib.rs index f16dcaff0..149912cc0 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -25,21 +25,18 @@ //! // foldr (foldl also available) //! let h2 = hlist![1, false, 42f32]; //! let folded = h2.foldr( -//! hlist![ -//! |i, acc| i + acc, -//! |_, acc| if acc > 42f32 { 9000 } else { 0 }, -//! |f, acc| f + acc -//! ], +//! hlist![|i, acc| i + acc, +//! |_, acc| if acc > 42f32 { 9000 } else { 0 }, +//! |f, acc| f + acc], //! 1f32 //! ); //! assert_eq!(folded, 9001); //! //! // Mapping over an HList //! let h3 = hlist![9000, "joe", 41f32]; -//! let mapped = h3.map(hlist![ -//! |n| n + 1, -//! |s| s, -//! |f| f + 1f32]); +//! let mapped = (&h3).map(hlist![|&n| n + 1, +//! |&s| s, +//! |&f| f + 1f32]); //! assert_eq!(mapped, hlist![9001, "joe", 42f32]); //! //! // Plucking a value out by type diff --git a/src/coproduct.rs b/src/coproduct.rs index a9163d0d3..73c2111fb 100644 --- a/src/coproduct.rs +++ b/src/coproduct.rs @@ -4,7 +4,7 @@ //! //! ``` //! # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { -//! type I32Bool = Coproduct!(i32, bool); +//! type I32Bool = Coprod!(i32, bool); //! let co1: I32Bool = into_coproduct(3); //! let co2: I32Bool = into_coproduct(true); //! @@ -18,6 +18,10 @@ //! let get_from_2b: Option<&bool> = co2.get(); //! assert_eq!(get_from_2a, None); //! assert_eq!(get_from_2b, Some(&true)); +//! +//! // *Taking* stuff (by value) +//! let take_from_1a: Option = co1.take(); +//! assert_eq!(take_from_1a, Some(3)); //! # } //! ``` //! @@ -28,23 +32,26 @@ //! # #[macro_use] extern crate frunk_core; //! # use frunk::hlist::*; //! # use frunk::coproduct::*; fn main() { -//! # type I32Bool = Coproduct!(i32, bool); +//! # type I32Bool = Coprod!(i32, bool); //! # let co1: I32Bool = into_coproduct(3); //! # let co2: I32Bool = into_coproduct(true); -//! let folder = hlist![ -//! |&i| format!("i32 {}", i), -//! |&b| String::from(if b { "t" } else { "f" }) -//! ]; //! -//! assert_eq!(co1.as_ref().fold(&folder), "i32 3".to_string()); -//! assert_eq!(co2.as_ref().fold(&folder), "t".to_string()); +//! // In the below, we use unimplemented!() to make it obvious hat we know what type of +//! // item is inside our coproducts co1 and co2 but in real life, you should be writing +//! // complete functions for all the cases when folding coproducts +//! assert_eq!( +//! co1.as_ref().fold(hlist![|&i| format!("i32 {}", i), +//! |&b| unimplemented!() /* we know this won't happen for co1 */ ]), +//! "i32 3".to_string()); +//! assert_eq!( +//! co2.as_ref().fold(hlist![|&i| unimplemented!() /* we know this won't happen for co2 */, +//! |&b| String::from(if b { "t" } else { "f" })]), +//! "t".to_string()); //! //! // There is also a value consuming-variant of fold //! -//! let folded = co1.fold(hlist![ -//! |i| format!("i32 {}", i), -//! |b| String::from(if b { "t" } else { "f" }) -//! ]); +//! let folded = co1.fold(hlist![|i| format!("i32 {}", i), +//! |b| String::from(if b { "t" } else { "f" })]); //! assert_eq!(folded, "i32 3".to_string()); //! # } //! ``` @@ -54,14 +61,14 @@ use frunk_core::hlist::*; /// Enum type representing a Coproduct. Think of this as a Result, but capable /// of supporting any arbitrary number of types instead of just 2. /// -/// To consctruct a Coproduct, you would typically declare a type using the `Coproduct!` type +/// To consctruct a Coproduct, you would typically declare a type using the `Coprod!` type /// macro and then use the `into_coproduct` method. /// /// # Examples /// /// ``` /// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { -/// type I32Bool = Coproduct!(i32, bool); +/// type I32Bool = Coprod!(i32, bool); /// let co1: I32Bool = into_coproduct(3); /// let get_from_1a: Option<&i32> = co1.get(); /// let get_from_1b: Option<&bool> = co1.get(); @@ -92,31 +99,31 @@ pub enum CNil {} /// /// ``` /// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { -/// type I32Bool = Coproduct!(i32, bool); +/// type I32Bool = Coprod!(i32, bool); /// let co1: I32Bool = into_coproduct(3); /// # } /// ``` #[macro_export] -macro_rules! Coproduct { +macro_rules! Coprod { // Nothing () => { $crate::coproduct::CNil }; // Just a single item ($single: ty) => { - $crate::coproduct::Coproduct<$single, CNil> + $crate::coproduct::Coproduct<$single, $crate::coproduct::CNil> }; ($first: ty, $( $repeated: ty ), +) => { - $crate::coproduct::Coproduct<$first, Coproduct!($($repeated), *)> + $crate::coproduct::Coproduct<$first, Coprod!($($repeated), *)> }; // <-- Forward trailing comma variants ($single: ty,) => { - Coproduct![$single] + Coprod![$single] }; ($first: ty, $( $repeated: ty, ) +) => { - Coproduct![$first, $($repeated),*] + Coprod![$first, $($repeated),*] }; // Forward trailing comma variants --> } @@ -147,7 +154,7 @@ impl IntoCoproduct> for Coproduct< /// /// ``` /// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { -/// type I32Bool = Coproduct!(i32, f32); +/// type I32Bool = Coprod!(i32, f32); /// let co1: I32Bool = into_coproduct(42f32); /// let get_from_1a: Option<&i32> = co1.get(); /// let get_from_1b: Option<&f32> = co1.get(); @@ -162,7 +169,22 @@ pub fn into_coproduct(to_into: I) -> C } // For turning something into a Coproduct --> -/// Trait for retrieving a coproduct element by type +/// Trait for retrieving a coproduct element reference by type. +/// +/// Returns an Option<&YourType> (notice that the inside of the option is a reference) +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { +/// type I32Bool = Coprod!(i32, f32); +/// let co1: I32Bool = into_coproduct(42f32); +/// let get_from_1a: Option<&i32> = co1.get(); +/// let get_from_1b: Option<&f32> = co1.get(); +/// assert_eq!(get_from_1a, None); +/// assert_eq!(get_from_1b, Some(&42f32)); +/// # } +/// ``` pub trait CoproductSelector { fn get(&self) -> Option<&S>; } @@ -190,6 +212,49 @@ impl CoproductSelector (notice that the inside of the option is a value) +/// +/// # Example +/// +/// ``` +/// # #[macro_use] extern crate frunk; use frunk::coproduct::*; fn main() { +/// type I32Bool = Coprod!(i32, f32); +/// let co1: I32Bool = into_coproduct(42f32); +/// let get_from_1a: Option = co1.take(); +/// let get_from_1b: Option = co1.take(); +/// assert_eq!(get_from_1a, None); +/// assert_eq!(get_from_1b, Some(42f32)); +/// # } +/// ``` +pub trait CoproductTaker { + fn take(self) -> Option; +} + +impl CoproductTaker for Coproduct { + fn take(self) -> Option { + use self::Coproduct::*; + match self { + Inl(thing) => Some(thing), + _ => None, // Impossible + } + } +} + +impl CoproductTaker> + for Coproduct + where Tail: CoproductTaker +{ + fn take(self) -> Option { + use self::Coproduct::*; + match self { + Inr(rest) => rest.take(), + _ => None, // Impossible + } + } +} + /// Trait for implementing "folding" a Coproduct into a value. /// /// The Folder should be an HList of closures that correspond (in order, for now..) to the @@ -201,7 +266,7 @@ impl CoproductSelector CoproductSelector { @@ -235,17 +298,17 @@ impl CoproductFoldable, R> for Coproduct } } -impl<'a, F, R, FTail, CH, CTail> CoproductFoldable<&'a HCons, R> for &'a Coproduct - where F: Fn(&'a CH) -> R, - &'a CTail: CoproductFoldable<&'a FTail, R> +impl<'a, F, R, FTail, CH, CTail> CoproductFoldable, R> for &'a Coproduct + where F: FnOnce(&'a CH) -> R, + &'a CTail: CoproductFoldable { - fn fold(self, f: &'a HCons) -> R { + fn fold(self, f: HCons) -> R { use self::Coproduct::*; - let ref f_head = f.head; - let ref f_tail = f.tail; + let f_head = f.head; + let f_tail = f.tail; match *self { Inl(ref r) => (f_head)(r), - Inr(ref rest) => <&'a CTail as CoproductFoldable<&'a FTail, R>>::fold(rest, f_tail), + Inr(ref rest) => <&'a CTail as CoproductFoldable>::fold(rest, f_tail), } } } @@ -260,13 +323,13 @@ impl CoproductFoldable for CNil { /// This is literally impossible; &CNil is not instantiable #[doc(hidden)] -impl<'a, F, R> CoproductFoldable<&'a F, R> for &'a CNil { - fn fold(self, _: &'a F) -> R { +impl<'a, F, R> CoproductFoldable for &'a CNil { + fn fold(self, _: F) -> R { unreachable!() } } -impl AsRef> for Coproduct { +impl AsRef> for Coproduct { fn as_ref(&self) -> &Coproduct { self } @@ -279,7 +342,7 @@ mod tests { #[test] fn test_into_coproduct() { - type I32StrBool = Coproduct!(i32, &'static str, bool); + type I32StrBool = Coprod!(i32, &'static str, bool); let co1: I32StrBool = into_coproduct(3); assert_eq!(co1, Inl(3)); @@ -299,30 +362,38 @@ mod tests { #[test] fn test_coproduct_fold_consuming() { - type I32StrBool = Coproduct!(i32, f32, bool); + type I32StrBool = Coprod!(i32, f32, bool); let co1: I32StrBool = into_coproduct(3); let folded = co1.fold(hlist![|i| format!("int {}", i), - |f| format!("float {}", f), - |b| (if b { "t" } else { "f" }).to_string()]); + |f| format!("float {}", f), + |b| (if b { "t" } else { "f" }).to_string()]); assert_eq!(folded, "int 3".to_string()); } #[test] fn test_coproduct_fold_non_consuming() { - type I32StrBool = Coproduct!(i32, f32, bool); + type I32StrBool = Coprod!(i32, f32, bool); let co1: I32StrBool = into_coproduct(3); let co2: I32StrBool = into_coproduct(true); let co3: I32StrBool = into_coproduct(42f32); - let folder = hlist![|&i| format!("int {}", i), - |&f| format!("float {}", f), - |&b| (if b { "t" } else { "f" }).to_string()]; - - assert_eq!(co1.as_ref().fold(&folder), "int 3".to_string()); - assert_eq!(co2.as_ref().fold(&folder), "t".to_string()); - assert_eq!(co3.as_ref().fold(&folder), "float 42".to_string()); + assert_eq!(co1.as_ref() + .fold(hlist![|&i| format!("int {}", i), + |&f| format!("float {}", f), + |&b| (if b { "t" } else { "f" }).to_string()]), + "int 3".to_string()); + assert_eq!(co2.as_ref() + .fold(hlist![|&i| format!("int {}", i), + |&f| format!("float {}", f), + |&b| (if b { "t" } else { "f" }).to_string()]), + "t".to_string()); + assert_eq!(co3.as_ref() + .fold(hlist![|&i| format!("int {}", i), + |&f| format!("float {}", f), + |&b| (if b { "t" } else { "f" }).to_string()]), + "float 42".to_string()); } } diff --git a/src/monoid.rs b/src/monoid.rs index ae89cdb6a..eaa1ec5e6 100644 --- a/src/monoid.rs +++ b/src/monoid.rs @@ -85,7 +85,8 @@ pub fn combine_n(o: &T, times: u32) -> T pub fn combine_all(xs: &Vec) -> T where T: Monoid + Semigroup + Clone { - xs.iter().fold(::empty(), |acc, next| acc.combine(&next)) + xs.iter() + .fold(::empty(), |acc, next| acc.combine(&next)) } impl Monoid for Option diff --git a/src/semigroup.rs b/src/semigroup.rs index 9c72d6b9e..c78977e5d 100644 --- a/src/semigroup.rs +++ b/src/semigroup.rs @@ -72,7 +72,9 @@ pub trait Semigroup { /// if all of the sub-element types are also Semiups impl Semigroup for HCons { fn combine(&self, other: &Self) -> Self { - self.tail.combine(&other.tail).prepend(self.head.combine(&other.head)) + self.tail + .combine(&other.tail) + .prepend(self.head.combine(&other.head)) } } diff --git a/src/validated.rs b/src/validated.rs index 35bfbb7c5..8460d20ee 100644 --- a/src/validated.rs +++ b/src/validated.rs @@ -44,7 +44,7 @@ //! # } //! ``` -use frunk_core::hlist::*; +use super::hlist::*; use std::ops::Add; /// A Validated is either an Ok holding an HList or an Err, holding a vector @@ -239,7 +239,6 @@ impl Add> for Validated #[cfg(test)] mod tests { - use frunk_core::hlist::*; use super::*; #[test] @@ -317,13 +316,14 @@ mod tests { fn test_to_result_ok() { let v = get_name(YahNah::Yah).into_validated() + get_age(YahNah::Yah) + get_email(YahNah::Yah); - let person = v.into_result().map(|hlist_pat!(name, age, email)| { - Person { - name: name, - age: age, - email: email, - } - }); + let person = v.into_result() + .map(|hlist_pat!(name, age, email)| { + Person { + name: name, + age: age, + email: email, + } + }); assert_eq!(person.unwrap(), Person { diff --git a/tests/coproduct_tests.rs b/tests/coproduct_tests.rs new file mode 100644 index 000000000..231f86060 --- /dev/null +++ b/tests/coproduct_tests.rs @@ -0,0 +1,41 @@ +#[macro_use] +extern crate frunk; +#[macro_use] +extern crate frunk_core; // for hlist macro +use frunk::coproduct::*; + +#[test] +fn test_into_coproduct() { + type I32StrBool = Coprod!(i32, &'static str, bool); + + let co1: I32StrBool = into_coproduct(3); + let get_from_1a: Option<&i32> = co1.get(); + let get_from_1b: Option<&bool> = co1.get(); + assert_eq!(get_from_1a, Some(&3)); + assert_eq!(get_from_1b, None); +} + +#[test] +fn test_coproduct_fold_consuming() { + type I32StrBool = Coprod!(i32, f32, bool); + + let co1: I32StrBool = into_coproduct(3); + let folded = co1.fold(hlist![|i| format!("int {}", i), + |f| format!("float {}", f), + |b| (if b { "t" } else { "f" }).to_string()]); + + assert_eq!(folded, "int 3".to_string()); +} + +#[test] +fn test_coproduct_fold_non_consuming() { + type I32StrBool = Coprod!(i32, f32, bool); + + let co: I32StrBool = into_coproduct(true);; + + assert_eq!(co.as_ref() + .fold(hlist![|&i| format!("int {}", i), + |&f| format!("float {}", f), + |&b| (if b { "t" } else { "f" }).to_string()]), + "t".to_string()); +} diff --git a/tests/generic_tests.rs b/tests/generic_tests.rs index cadd7f472..d572e7ccf 100644 --- a/tests/generic_tests.rs +++ b/tests/generic_tests.rs @@ -2,7 +2,6 @@ extern crate frunk; #[macro_use] // for the hlist macro extern crate frunk_core; -use frunk::hlist::*; use frunk::generic::*; mod common; From 050d14a5e11a69acbf5a4c9fed7c0e529403a25c Mon Sep 17 00:00:00 2001 From: Lloyd Date: Thu, 20 Apr 2017 13:36:45 +0900 Subject: [PATCH 8/8] Release 0.1.25 --- Cargo.toml | 8 ++++---- core/Cargo.toml | 4 ++-- derives/Cargo.toml | 4 ++-- laws/Cargo.toml | 5 ++--- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4faf44af..e1f042bf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk" -version = "0.1.24" +version = "0.1.25" authors = ["Lloyd "] description = "Frunk provides developers with a number of functional programming tools like HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid, Semigroup and friends." license = "MIT" @@ -16,12 +16,12 @@ time = "0.1.36" [dependencies.frunk_core] path = "core" -version = "0.0.12" +version = "0.0.13" [dependencies.frunk_derives] path = "derives" -version = "0.0.13" +version = "0.0.14" [dev-dependencies.frunk_laws] path = "laws" -version = "0.0.2" \ No newline at end of file +version = "0.0.3" \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index d9d9df4cd..a62bd1036 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk_core" -version = "0.0.12" +version = "0.0.13" authors = ["Lloyd "] description = "Frunk core provides developers with HList and Generic" license = "MIT" @@ -13,4 +13,4 @@ travis-ci = { repository = "lloydmeta/frunk" } [dev-dependencies.frunk_derives] path = "../derives" -version = "0.0.13" +version = "0.0.14" diff --git a/derives/Cargo.toml b/derives/Cargo.toml index 621a189bc..90be4b676 100644 --- a/derives/Cargo.toml +++ b/derives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk_derives" -version = "0.0.13" +version = "0.0.14" authors = ["Lloyd "] description = "frunk_derives contains the custom derivations for certain traits in Frunk." license = "MIT" @@ -20,4 +20,4 @@ quote = "0.3.15" [dependencies.frunk_core] path = "../core" -version = "0.0.12" +version = "0.0.13" diff --git a/laws/Cargo.toml b/laws/Cargo.toml index 6619ab291..e16558d56 100644 --- a/laws/Cargo.toml +++ b/laws/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "frunk_laws" -version = "0.0.2" +version = "0.0.3" authors = ["Lloyd "] description = "frunk_laws contains laws for algebras declared in Frunk." license = "MIT" @@ -13,8 +13,7 @@ travis-ci = { repository = "lloydmeta/frunk" } [dependencies.frunk] path = ".." -version = "0.1.13" - +version = "0.1.25" [dependencies] quickcheck = "0.3" \ No newline at end of file