diff options
| author | Andreas Grois <andi@grois.info> | 2023-04-02 21:38:17 +0200 |
|---|---|---|
| committer | Andreas Grois <andi@grois.info> | 2023-04-02 21:53:09 +0200 |
| commit | 418f514fc46f45ae2901753e3398adb33664bed9 (patch) | |
| tree | f8edc0df880a00d4964877d4b835c0eb9e481210 | |
| parent | 02d01dd1b544a576caeb8da42912c5db904b94bd (diff) | |
I, for one, welcome our new clippy overlords
| -rw-r--r-- | clippy.toml | 1 | ||||
| -rw-r--r-- | examples/text-adventure/data.rs | 25 | ||||
| -rw-r--r-- | examples/text-adventure/dsl.rs | 2 | ||||
| -rw-r--r-- | examples/text-adventure/logic.rs | 22 | ||||
| -rw-r--r-- | examples/text-adventure/main.rs | 16 | ||||
| -rw-r--r-- | examples/text-adventure/side_effects.rs | 10 | ||||
| -rw-r--r-- | src/lib.rs | 242 | ||||
| -rw-r--r-- | tests/multiple_generics.rs | 49 | ||||
| -rw-r--r-- | tests/multiple_generics_lifetime.rs | 35 | ||||
| -rw-r--r-- | tests/trivial.rs | 8 | ||||
| -rw-r--r-- | tests/trivial_lifetime.rs | 8 | ||||
| -rw-r--r-- | tests/vector.rs | 149 | ||||
| -rw-r--r-- | tests/with_lifetimes.rs | 2 |
13 files changed, 261 insertions, 308 deletions
diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..12b8e80 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = [ "eDSL", "eDSLs", ".." ]
\ No newline at end of file diff --git a/examples/text-adventure/data.rs b/examples/text-adventure/data.rs index 2c12bb0..3e9c49e 100644 --- a/examples/text-adventure/data.rs +++ b/examples/text-adventure/data.rs @@ -83,19 +83,13 @@ pub enum Item{ impl Item { fn price(self) -> usize { match self { - Item::SausageRoll => 300, - Item::Pickles => 250, - Item::Milk => 125, - Item::Yoghurt => 125, + Item::SausageRoll | Item::FishSandwich | Item::Shots => 300, + Item::Pickles | Item::Pulp => 250, + Item::Milk | Item::Yoghurt | Item::Beer => 125, Item::Cheese => 750, Item::CatFood => 2500, - Item::Beer => 125, Item::ToiletPaper => 500, - Item::FishSandwich => 300, Item::ChewingGum => 100, - Item::Shots => 300, - Item::Pulp => 250, - } } pub fn description(self) -> &'static str { @@ -112,7 +106,6 @@ impl Item { Item::ChewingGum => "A pack of chewing gum, costing €1.00", Item::Shots => "A shot of a sad excuse for whisky, costing €3.00", Item::Pulp => "A pulp novel called \"Aliens ate my trashbin\", which should not cost the €2.50 it does", - } } } @@ -131,17 +124,11 @@ impl Location{ //In a real project I would probably aim to make this Copy as well, especially if it's as small as this. //I left it as Clone intentionally, to illustrate how one can work around the limitation of it not being Copy. -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Inventory { pub items : Vec<Item>, } -impl Default for Inventory{ - fn default() -> Self { - Self { items: Default::default() } - } -} - impl Inventory{ pub fn has_item_from_room(&self, room : Location) -> bool { let items_from_room = room.items(); @@ -151,7 +138,7 @@ impl Inventory{ if self.items.len() < 3 { let mut items = self.items; items.push(item); //am I the only one that hates that push doesn't return the updated vec? - Ok(Inventory{items, ..self}) + Ok(Inventory{items}) } else { Err(self) } @@ -170,7 +157,7 @@ impl Inventory{ "€10.00" //It doesn't change. Can as well be a constant. } pub fn total_price(&self) -> usize { - self.items.iter().cloned().map(Item::price).sum::<usize>() + self.items.iter().copied().map(Item::price).sum::<usize>() } pub fn can_afford(&self) -> bool { self.total_price() <= 1000_usize diff --git a/examples/text-adventure/dsl.rs b/examples/text-adventure/dsl.rs index 44bc978..c448f04 100644 --- a/examples/text-adventure/dsl.rs +++ b/examples/text-adventure/dsl.rs @@ -5,7 +5,7 @@ use std::{rc::Rc, borrow::Cow}; use higher_free_macro::free; use std::convert::identity; use higher::Functor; -use super::data::*; +use super::data::{Location, Mood, Speaker}; #[derive(Clone)] pub enum SausageRoll<'a, 's,A>{ diff --git a/examples/text-adventure/logic.rs b/examples/text-adventure/logic.rs index dc3ff45..d884598 100644 --- a/examples/text-adventure/logic.rs +++ b/examples/text-adventure/logic.rs @@ -4,8 +4,8 @@ use std::borrow::Cow; use higher::{run, Functor, Pure, Bind}; -use super::data::*; -use super::dsl::*; +use super::data::{Inventory, Item, Location, Mood, Speaker}; +use super::dsl::{FreeSausageRoll, exposition, give_player_options, present_location, say_dialogue_line}; //Haskell has a when function, and it's nice. Sooo, copy that. macro_rules! when { @@ -24,7 +24,7 @@ pub fn game<'a,'s : 'a>() -> FreeSausageRoll<'a, 's, ()>{ c <= intro(); when!{c => { //handle_rooms is the main game loop: Go from room to room. - c <= handle_rooms(Location::Refrigerators, Default::default()); //can't destructure in assignment in higher-0.2. Maybe later. + c <= handle_rooms(Location::Refrigerators, Inventory::default()); //can't destructure in assignment in higher-0.2. Maybe later. //if we ended up here, we left the supermarket. ending(c.1) }} @@ -66,7 +66,7 @@ fn ending<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ()>{ } else { run!{ say_dialogue_line(Speaker::Partner, Cow::from("What did you do in there? I asked you to bring me a sausage roll..."), Mood::Annoyed); - when!{inventory.items.len() > 0 => { + when!{!inventory.items.is_empty() => { say_dialogue_line(Speaker::Partner, Cow::from("Also, why did you buy all that other stuff?"), Mood::Annoyed) }}; say_dialogue_line(Speaker::Partner, Cow::from("Well, let's move on, but don't complain if I get hangry on the way."), Mood::Annoyed) @@ -77,13 +77,11 @@ fn ending<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ()>{ fn handle_rooms<'a,'s:'a>(room: Location, inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{ run!{ c <= handle_room(room, inventory); - if c.0 != Location::Entrance { + if c.0 == Location::Entrance { + FreeSausageRoll::pure(c) + } else { //If this were an actual game, we could put a save-point here. At this location the next room to handle is just determined by room and inventory. handle_rooms(c.0, c.1) - } else { - run!{ - yield c - } } } } @@ -185,7 +183,7 @@ fn handle_deli<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Loca 3 => { run!{ i <= talk_to_deli_lady(inventory.clone()); - handle_deli(i.clone()) + handle_deli(i) } }, _ => unreachable!() @@ -215,7 +213,7 @@ fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ( FreeSausageRoll::pure((Location::Entrance, inventory.clone())) } }, - Err(inventory) => handle_checkout(inventory.clone()), + Err(inventory) => handle_checkout(inventory), } } }, @@ -275,7 +273,7 @@ fn return_item<'a,'s:'a>(inventory : Inventory, room : Location) -> FreeSausageR //similar to try_take_item here the inventory can't easily be captured. For illustration purposes, here we //don't pass it along as clones, but rather return from do-notation to capture it. let items_in_room = room.items(); - let carried_items_from_here = inventory.items.iter().filter(|i| items_in_room.contains(i)).cloned().collect::<Vec<_>>(); + let carried_items_from_here = inventory.items.iter().filter(|i| items_in_room.contains(i)).copied().collect::<Vec<_>>(); //ownership shmownership let carried_items_from_here2 = carried_items_from_here.clone(); let chosen = run! { diff --git a/examples/text-adventure/main.rs b/examples/text-adventure/main.rs index c98441b..1d538cc 100644 --- a/examples/text-adventure/main.rs +++ b/examples/text-adventure/main.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! A small example text adventure, the logic of which is implemented as a Free Monad based eDSL. //! //! The goal of this game is to buy a sausage roll. With pickle. @@ -11,17 +13,17 @@ //! //! In a real project, I'd just make all game state (here: inventory) `Copy`, and use owned values wherever possible to make the code //! more concise. If `Copy` is not an option, I'd probably make a custom version of `run!{}` that allows to clone the -//! game state in a convenient way (see https://github.com/bodil/higher/issues/6). +//! game state in a convenient way (see [higher issue 6](https://github.com/bodil/higher/issues/6)). //! //! But on to the explanation what is going on: //! This project has 4 modules: -//! - "data" contains the data. Stuff like item types, item descriptions, rooms, etc. -//! - "dsl" contains the embedded domain specific language. In other words, a Functor and the corresponding Free Monad type (and some helpers) -//! - "logic" describes the game's main logic using the language defined in "dsl" -//! - "side_effects" actually runs the logic. +//! - `data` contains the data. Stuff like item types, item descriptions, rooms, etc. +//! - `dsl` contains the embedded domain specific language. In other words, a Functor and the corresponding Free Monad type (and some helpers) +//! - `logic` describes the game's main logic using the language defined in "dsl" +//! - `side_effects` actually runs the logic. //! -//! The important part here is that all the stuff that isn't in "side_effects" is independent of the concrete implementation of "side_effects". -//! The current "side_effects" runs a text-adventure, but it could just as well render as a visual-novel, without the need to touch any of the other modules. +//! The important part here is that all the stuff that isn't in `side_effects` is independent of the concrete implementation of `side_effects`. +//! The current `side_effects` runs a text-adventure, but it could just as well render as a visual-novel, without the need to touch any of the other modules. mod data; mod dsl; diff --git a/examples/text-adventure/side_effects.rs b/examples/text-adventure/side_effects.rs index ed0f9e1..dd911dd 100644 --- a/examples/text-adventure/side_effects.rs +++ b/examples/text-adventure/side_effects.rs @@ -11,23 +11,23 @@ pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result while let FreeSausageRoll::Free(command) = game { game = match *command { crate::dsl::SausageRoll::SayDialogueLine { speaker, text, mood, next } => { - println!("{} says: \"{}\" with {} on their face.", speaker.text_description(), text, mood.text_description()); + println!("{} says: \"{text}\" with {} on their face.", speaker.text_description(), mood.text_description()); next }, crate::dsl::SausageRoll::GivePlayerOptions { options, next } => { println!("Your options are:"); for (id, option) in options.iter().enumerate().map(|(i,o)| (i+1,o)){ - println!("{}: {}", id, option); + println!("{id}: {option}"); } let mut input = String::new(); let mut chosen; - while let None = { + while { input.clear(); std::io::stdin().read_line(&mut input)?; chosen = input.trim().parse().ok().filter(|o : &usize| *o > 0 && *o <= options.len()); - chosen + chosen.is_none() } { println!("Invalid choice. Please select one of the options given above."); } @@ -39,7 +39,7 @@ pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result next }, crate::dsl::SausageRoll::Exposition { text, next } => { - println!("{}", text); + println!("{text}"); next }, }; @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! A macro that uses the traits from the [higher] crate and generates a Free [`Monad`][higher::Monad] type for a given [`Functor`][higher::Functor]. //! //! This is a port of the Control.Monad.Free part of the ["free" Haskell package](https://hackage.haskell.org/package/free) by Edward Kmett. @@ -359,17 +361,17 @@ mod free_monad_tests{ #[test] fn test_lift_f_no_lifetime(){ - let f = FreeVec::lift_f(vec![1,2,3]); - match f { - FreeVec::Free(v) => { - match &**v { + let free = FreeVec::lift_f(vec![1,2,3]); + match free { + FreeVec::Free(vector) => { + match &**vector { [FreeVec::Pure(a),FreeVec::Pure(b),FreeVec::Pure(c)] => { assert_eq!(vec![*a,*b,*c], vec![1,2,3]); }, _ => unreachable!() } }, - _ => unreachable!() + FreeVec::Pure(_) => unreachable!() } } @@ -392,33 +394,41 @@ mod free_monad_tests{ #[test] fn test_fmap_no_lifetime(){ let f = FreeVec::lift_f(vec![1,2,3]); - let f = f.fmap(|x| (x as f32)/2.0); + let f = f.fmap(|x| (f64::from(x))/2.0); match f { FreeVec::Free(f) => { match &**f{ [FreeVec::Pure(a), FreeVec::Pure(b), FreeVec::Pure(c)] => { - assert_eq!(vec![0.5f32, 1f32, 1.5f32], vec![*a,*b,*c]); + assert_eq!(vec![0.5f64, 1f64, 1.5f64], vec![*a,*b,*c]); }, _ => unreachable!() } }, - _ => unreachable!() + FreeVec::Pure(_) => unreachable!() } } + //just to appease clippy without disabling the lint.... + macro_rules! assert_nearly_equal { + ($a:expr, $b:expr, $c:expr) => { + assert!((($a)-($b)).abs() < $c) + }; + } + #[test] fn test_bind_no_lifetime(){ let f = FreeVec::lift_f(vec![1,2]); - let f = f.bind(|x| if x % 2 == 0 { FreeVec::lift_f(vec![x as f32,x as f32 + 1.0f32])} else { FreeVec::Pure(x as f32)}); + let f = f.bind(|x| if x % 2 == 0 { FreeVec::lift_f(vec![f64::from(x),f64::from(x) + 1.0f64])} else { FreeVec::Pure(f64::from(x))}); match f { FreeVec::Free(f) => { match &**f { [FreeVec::Pure(a),FreeVec::Free(b)] => { - assert_eq!(*a, 1.0f32); + + assert_nearly_equal!(*a, 1.0f64, f64::EPSILON); match &***b { [FreeVec::Pure(a), FreeVec::Pure(b)] => { - assert_eq!(*a, 2.0f32); - assert_eq!(*b, 3.0f32); + assert_nearly_equal!(*a, 2.0f64, f64::EPSILON); + assert_nearly_equal!(*b, 3.0f64, f64::EPSILON); }, _ => unreachable!() } @@ -426,27 +436,27 @@ mod free_monad_tests{ _ => unreachable!() } }, - _ => unreachable!() + FreeVec::Pure(_) => unreachable!() } } #[test] fn test_apply_no_lifetime(){ - let f = FreeVec::Free(Box::new(vec![FreeVec::Free(Box::new(vec![FreeVec::Pure((|x| (x as i32)*2) as fn(u32) -> i32), FreeVec::Pure((|x| (x as i32)+2) as fn(u32)->i32)])), FreeVec::Pure((|x| (x as i32)-5) as fn(u32)->i32)])); - let m = FreeVec::Free(Box::new(vec![FreeVec::Pure(5u32), FreeVec::Free(Box::new(vec![FreeVec::Pure(6u32), FreeVec::Pure(7u32)]))])); - let m = m.apply(f.fmap(Into::into)); + let functions = FreeVec::Free(Box::new(vec![FreeVec::Free(Box::new(vec![FreeVec::Pure((|x| i64::from(x)*2) as fn(u32) -> i64), FreeVec::Pure((|x| i64::from(x)+2) as fn(u32)->i64)])), FreeVec::Pure((|x| i64::from(x)-5) as fn(u32)->i64)])); + let free_monad = FreeVec::Free(Box::new(vec![FreeVec::Pure(5u32), FreeVec::Free(Box::new(vec![FreeVec::Pure(6u32), FreeVec::Pure(7u32)]))])); + let free_monad = free_monad.apply(functions.fmap(Into::into)); //what have I gotten myself into... //at least the mapped sub-trees are all identical in shape to m, so, they can be tested by the same test function... - let check_mlike_structure = |m : &FreeVec<_>, p1,p2,p3| { - match m { - FreeVec::Free(m) => { - match &***m{ - [FreeVec::Pure(l),FreeVec::Free(r)] => { - assert_eq!(*l,p1); - match &***r{ - [FreeVec::Pure(l), FreeVec::Pure(r)] => { - assert_eq!(*l, p2); - assert_eq!(*r, p3); + let check_mlike_structure = |free_monad : &FreeVec<_>, pure1,pure2,pure3| { + match free_monad { + FreeVec::Free(vector) => { + match &***vector{ + [FreeVec::Pure(left),FreeVec::Free(right)] => { + assert_eq!(*left,pure1); + match &***right{ + [FreeVec::Pure(left), FreeVec::Pure(right)] => { + assert_eq!(*left, pure2); + assert_eq!(*right, pure3); }, _ => unreachable!() } @@ -454,27 +464,27 @@ mod free_monad_tests{ _ => unreachable!() } }, - _ => unreachable!() + FreeVec::Pure(_) => unreachable!() } }; //now, where are those sub-trees exactly, in this monstrosity? - match m { - FreeVec::Free(m) => { - match &**m{ - [FreeVec::Free(l), r] => { - match &***l { + match free_monad { + FreeVec::Free(vector) => { + match &**vector{ + [FreeVec::Free(left), right] => { + match &***left { [a,b] => { - check_mlike_structure(a, 10,12,14); - check_mlike_structure(b, 7,8,9); + check_mlike_structure(a, 10i64,12i64,14i64); + check_mlike_structure(b, 7i64,8i64,9i64); }, _ => unreachable!() } - check_mlike_structure(r, 0,1,2) + check_mlike_structure(right, 0i64,1i64,2i64); }, _ => unreachable!() } }, - _ => unreachable!() + FreeVec::Pure(_) => unreachable!() } } @@ -495,7 +505,7 @@ mod free_monad_tests{ } //need Bind and Pure to test retract. This is dumb, but it should fulfill the monad laws: - impl<'a, A : 'a, B : 'a> Bind<'a, A> for Conti<'a,A,B> where B : Clone{ + impl<'a, A : 'a, B : Clone+'a> Bind<'a, A> for Conti<'a,A,B>{ type Target<T> = Conti<'a,T,B>; fn bind<C, F>(self, f: F) -> Self::Target<C> where F: Fn(A) -> Self::Target<C> + 'a { @@ -506,7 +516,7 @@ mod free_monad_tests{ Conti(Rc::new(move |x| (l(x.clone())).0(x)), Rc::new(move |x| (r(x.clone())).1(x))) } } - impl<'a, A : 'a, B : 'a> Pure<A> for Conti<'a,A,B> where A : Clone{ + impl<'a, A : Clone+'a, B : 'a> Pure<A> for Conti<'a,A,B>{ fn pure(value: A) -> Self { let v2 = value.clone(); Conti(Rc::new(move |_| value.clone()), Rc::new(move |_| v2.clone())) @@ -516,33 +526,31 @@ mod free_monad_tests{ //I really am not certain if the Pure and Bind above are correct. Sooo, why not test those too, while we are at it? #[test] fn test_conti_monad_laws(){ - let b = |x : u32| { - let y = x.clone(); - Conti::<u32,u32>(Rc::new(move |a| x.clone() + a*2), Rc::new(move |a| y.clone() * a+3)) + let binder = |x : u32| { + let y = x; + Conti::<u32,u32>(Rc::new(move |a| x + a*2), Rc::new(move |a| y * a+3)) }; - let t1 = Conti::pure(7u32); - let v1 = t1.bind(b); - let v2 = b(7u32); + let test1 = Conti::pure(7u32); + let v1 = test1.bind(binder); + let v2 = binder(7u32); assert_eq!((v1.0)(13), (v2.0)(13)); assert_eq!((v1.1)(17), (v2.1)(17)); - let c = Conti(Rc::new(|a| 31 + a*5), Rc::new(|b| 32*b+3)); - let d =c.clone().bind(Conti::pure); - assert_eq!((c.0)(3), (d.0)(3)); - assert_eq!((c.1)(5), (d.1)(5)); + let test2 = Conti(Rc::new(|a| 31 + a*5), Rc::new(|b| 32*b+3)); + let bound_with_pure =test2.clone().bind(Conti::pure); + assert_eq!((test2.0)(3), (bound_with_pure.0)(3)); + assert_eq!((test2.1)(5), (bound_with_pure.1)(5)); - let m = Conti(Rc::new(|a| 32 + (a*2)), Rc::new(|b| 32*b+7)); + let test3 = Conti(Rc::new(|a| 32 + (a*2)), Rc::new(|b| 32*b+7)); let g = |x : u32| { - let y = x.clone(); - Conti::<u32,u32>(Rc::new(move |a| x.clone()*2 + a), Rc::new(move |a| y.clone() * a+7)) + Conti::<u32,u32>(Rc::new(move |a| x*2 + a), Rc::new(move |a| x * a+7)) }; let h = |x : u32| { - let y = x.clone(); - Conti::<u32,u32>(Rc::new(move |a| x.clone() + a), Rc::new(move |a| y.clone() * a+12)) + Conti::<u32,u32>(Rc::new(move |a| x + a), Rc::new(move |a| x * a+12)) }; - let v1 = (m.clone().bind(g.clone())).bind(h.clone()); - let v2 = m.bind(|a| (g(a).bind(h))); + let v1 = (test3.clone().bind(g)).bind(h); + let v2 = test3.bind(|a| (g(a).bind(h))); assert_eq!((v1.0)(37), (v2.0)(37)); assert_eq!((v1.1)(41), (v2.1)(41)); @@ -559,14 +567,14 @@ mod free_monad_tests{ FreeConti::Free(m) => { match (m.0)(4){ FreeConti::Pure(v) => assert_eq!(v, 8), - _ => unreachable!() + FreeConti::Free(_) => unreachable!() } match (m.1)(4){ FreeConti::Pure(v) => assert_eq!(v, 9), - _ => unreachable!() + FreeConti::Free(_) => unreachable!() } }, - _ => unreachable!() + FreeConti::Pure(_) => unreachable!() } } @@ -580,27 +588,27 @@ mod free_monad_tests{ #[test] fn test_fmap_lifetime(){ - let c = Conti(Rc::new(|x : u32| (x as i32)*3+2), Rc::new(|x| ((x as i32)+2)*5)); - let f = FreeConti::lift_f(c); - let f = f.fmap(|x : i32| (x as f32)*0.25f32); - match f { + let functor = Conti(Rc::new(|x : u32| i32::try_from(x).unwrap()*3+2), Rc::new(|x| (i32::try_from(x).unwrap()+2)*5)); + let free_monad = FreeConti::lift_f(functor); + let free_monad = free_monad.fmap(|x : i32| f64::from(x)*0.25f64); + match free_monad { FreeConti::Free(f) => { - let l = (f.0)(7); - match l { + let left = (f.0)(7); + match left { FreeConti::Pure(v) => { - assert_eq!(v, 5.75f32); + assert_nearly_equal!(v, 5.75f64, f64::EPSILON); }, - _ => unreachable!() + FreeConti::Free(_) => unreachable!() } - let r = (f.1)(7); - match r { + let right = (f.1)(7); + match right { FreeConti::Pure(v) => { - assert_eq!(v, 11.25f32); + assert_nearly_equal!(v, 11.25f64, f64::EPSILON); }, - _ => unreachable!() + FreeConti::Free(_) => unreachable!() } }, - _ => unreachable!() + FreeConti::Pure(_) => unreachable!() } } @@ -615,101 +623,101 @@ mod free_monad_tests{ #[test] fn test_bind_lifetime(){ - let c = Conti(Rc::new(|x : u32| (x as i32)*3+2), Rc::new(|x| ((x as i32)+2)*5)); - let f = FreeConti::lift_f(c); - let f = f.bind(|y| { - let z = y.clone(); - FreeConti::lift_f(Conti(Rc::new(move |x| (x as f32)*0.25f32 + (y as f32)), Rc::new(move |x| (x as f32) * 0.5f32 - (z as f32)))) + let functor = Conti(Rc::new(|x : u32| i32::try_from(x).unwrap()*3+2), Rc::new(|x| (i32::try_from(x).unwrap()+2)*5)); + let free_monad = FreeConti::lift_f(functor); + let free_monad = free_monad.bind(|y| { + let z = y; + FreeConti::lift_f(Conti(Rc::new(move |x| f64::from(x)*0.25f64 + f64::from(y)), Rc::new(move |x| f64::from(x) * 0.5f64 - f64::from(z)))) }); - match f { + match free_monad { FreeConti::Free(f) => { - let l = (f.0)(4); - match l { + let left = (f.0)(4); + match left { FreeConti::Free(f) => { //14i32 - let l = (f.0)(5); - match l { - FreeConti::Pure(v) => assert_eq!(v, 15.25f32), + let left = (f.0)(5); + match left { + FreeConti::Pure(v) => assert_nearly_equal!(v, 15.25f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - let r = (f.1)(5); - match r{ - FreeConti::Pure(v) => assert_eq!(v, -11.5f32), + let right = (f.1)(5); + match right{ + FreeConti::Pure(v) => assert_nearly_equal!(v, -11.5f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } }, FreeConti::Pure(_) => unreachable!(), } - let r = (f.1)(4); - match r { + let right = (f.1)(4); + match right { FreeConti::Free(f) => { //30i32 - let l = (f.0)(5); - match l { - FreeConti::Pure(v) => assert_eq!(v, 31.25f32), + let left = (f.0)(5); + match left { + FreeConti::Pure(v) => assert_nearly_equal!(v, 31.25f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - let r = (f.1)(5); - match r { - FreeConti::Pure(v) => assert_eq!(v, -27.5f32), + let right = (f.1)(5); + match right { + FreeConti::Pure(v) => assert_nearly_equal!(v, -27.5f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } }, FreeConti::Pure(_) => unreachable!(), } }, - _ => unreachable!() + FreeConti::Pure(_) => unreachable!() } } #[test] fn test_apply_lifetime(){ //oh, god, please no. - let m = FreeConti::lift_f(Conti(Rc::new(|x : u32| (x as i32)*3+2), Rc::new(|x| ((x as i32)+2)*5))); - let f = FreeConti::lift_f(Conti(Rc::new(|x : u32| -> ApplyFn<i32,f32> { - (move |y : i32| (y + (x as i32)) as f32).into() + let free_monad_input = FreeConti::lift_f(Conti(Rc::new(|x : u32| i32::try_from(x).unwrap()*3+2), Rc::new(|x| (i32::try_from(x).unwrap()+2)*5))); + let functions = FreeConti::lift_f(Conti(Rc::new(|x : u32| -> ApplyFn<i32,f64> { + (move |y : i32| f64::from(y + i32::try_from(x).unwrap())).into() }), Rc::new(|x : u32| { - (move |y : i32| (y*(x as i32)) as f32).into() + (move |y : i32| f64::from(y*i32::try_from(x).unwrap())).into() }))); //make it stop! - let m = m.apply(f); + let free_monad = free_monad_input.apply(functions); - match m { + match free_monad { FreeConti::Free(m) => { - let l = (m.0)(5u32); - match l { + let left = (m.0)(5u32); + match left { FreeConti::Free(m) => { - let l = (m.0)(7u32); - match l { - FreeConti::Pure(v) => assert_eq!(v,28f32), + let left = (m.0)(7u32); + match left { + FreeConti::Pure(v) => assert_nearly_equal!(v,28f64,f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - let r = (m.1)(7u32); - match r { - FreeConti::Pure(v) => assert_eq!(v,50f32), + let right = (m.1)(7u32); + match right { + FreeConti::Pure(v) => assert_nearly_equal!(v,50f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } }, FreeConti::Pure(_) => unreachable!(), } - let r = (m.1)(5u32); - match r { + let right = (m.1)(5u32); + match right { FreeConti::Free(m) => { - let l = (m.0)(7u32); - match l { - FreeConti::Pure(v) => assert_eq!(v,(5f32*23f32)), + let left = (m.0)(7u32); + match left { + FreeConti::Pure(v) => assert_nearly_equal!(v,5f64*23f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - let r = (m.1)(7u32); - match r { - FreeConti::Pure(v) => assert_eq!(v, 5f32*45f32), + let right = (m.1)(7u32); + match right { + FreeConti::Pure(v) => assert_nearly_equal!(v, 5f64*45f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } }, FreeConti::Pure(_) => unreachable!(), } }, - _ => unreachable!() + FreeConti::Pure(_) => unreachable!() } //let's never speak of this again. } diff --git a/tests/multiple_generics.rs b/tests/multiple_generics.rs index 2f560dd..c8c76ba 100644 --- a/tests/multiple_generics.rs +++ b/tests/multiple_generics.rs @@ -1,11 +1,21 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! Tests if multiple generic parameters work, for the case that lifetimes are independent of mapping functions. -//! For simplicity, it just creates a FreeResult +//! For simplicity, it just creates a `FreeResult` based on `Result`. use higher_free_macro::free; use higher::{Functor, Bind, Apply}; free!(FreeResult<O,E>, Result<FreeResult<O,E>,E>); + +//just to appease clippy without disabling the lint.... +macro_rules! assert_nearly_equal { + ($a:expr, $b:expr, $c:expr) => { + assert!((($a)-($b)).abs() < $c) + }; +} + #[test] fn test_multiple_generics(){ let m : FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); @@ -16,26 +26,16 @@ fn test_multiple_generics(){ match m { FreeResult::Free(b) => { match *b { - Ok(f) => { - match f { - FreeResult::Free(b) => { - match *b { - Ok(f) => { - match f{ - FreeResult::Pure(x) => assert_eq!(x, 37*6), - _ => unreachable!() - } - } - _ => unreachable!() - } - }, + Ok(FreeResult::Free(b)) => { + match *b { + Ok(FreeResult::Pure(x)) => assert_eq!(x, 37*6), _ => unreachable!() } } _ => unreachable!() } } - _ => unreachable!() + FreeResult::Pure(_) => unreachable!() } } @@ -46,41 +46,36 @@ fn test_multiple_generics2(){ match m{ FreeResult::Free(m) => { match &*m { - Ok(m) => { - match m{ - FreeResult::Free(m) => { - match &**m { - Err(e) => assert_eq!(e, "An early out."), - _ => unreachable!() - } - } + Ok(FreeResult::Free(m)) => { + match &**m { + Err(e) => assert_eq!(e, "An early out."), _ => unreachable!() } }, _ => unreachable!() } }, - _ => unreachable!() + FreeResult::Pure(_) => unreachable!() } } #[test] fn test_multiple_generics3(){ let m : FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); - let f : FreeResult<_, String> = FreeResult::Pure(|x : u32| -> f32 {(x as f32)*0.5f32}).fmap(Into::into); + let f : FreeResult<_, String> = FreeResult::Pure(|x : u32| -> f64 {f64::from(x)*0.5f64}).fmap(Into::into); let m = m.apply(f); match m{ FreeResult::Free(m) => { match &*m{ Ok(k) => { match k { - FreeResult::Pure(k) => assert_eq!(18.5f32, *k), + FreeResult::Pure(k) => assert_nearly_equal!(18.5f64, *k, f64::EPSILON), FreeResult::Free(_) => unreachable!(), } } Err(_) => unreachable!(), } }, - _ => unreachable!() + FreeResult::Pure(_) => unreachable!() } }
\ No newline at end of file diff --git a/tests/multiple_generics_lifetime.rs b/tests/multiple_generics_lifetime.rs index ddc0628..48a9bd1 100644 --- a/tests/multiple_generics_lifetime.rs +++ b/tests/multiple_generics_lifetime.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! Tests if multiple generic parameters work, if the return value's lifetime depends on the mapping function lifetime. use std::rc::Rc; @@ -24,10 +26,10 @@ free!(<'xx>, FreeTest<'xx,'yy,AA,BB>, TestFunctor<'xx, 'yy, FreeTest<'xx, 'yy, A #[test] fn test_lifetime_multiple_generics(){ - let m = FreeTest::lift_f(TestFunctor{ data : &"Listening to NSP while writing this.", next : Rc::new(|x| (x as f32)*0.5f32)}); - let f = FreeTest::Pure(|x : f32| -> bool {x > 0.7f32} ).fmap(Into::into); - let m = m.apply(f); - match m { + let free_monad = FreeTest::lift_f(TestFunctor{ data : &"Listening to NSP while writing this.", next : Rc::new(|x| f64::from(x)*0.5f64)}); + let functions = FreeTest::Pure(|x : f64| -> bool {x > 0.7f64} ).fmap(Into::into); + let free_monad_after_apply = free_monad.apply(functions); + match free_monad_after_apply { FreeTest::Free(m) => { assert_eq!(m.data, &"Listening to NSP while writing this."); let x = m.next.clone(); @@ -43,24 +45,31 @@ fn test_lifetime_multiple_generics(){ FreeTest::Free(_) => unreachable!(), } }, - _ => unreachable!() + FreeTest::Pure(_) => unreachable!() } } +//just to appease clippy without disabling the lint.... +macro_rules! assert_nearly_equal { + ($a:expr, $b:expr, $c:expr) => { + assert!((($a)-($b)).abs() < $c) + }; +} + #[test] fn test_lifetime_multiple_generics_bind(){ - let m = FreeTest::lift_f(TestFunctor{ data : &"Listening to Soilwork while writing this.", next : Rc::new(|x| (x as f32)*0.5f32)}); - let m = m.bind(|x : f32| -> FreeTest<_,_> { + let m = FreeTest::lift_f(TestFunctor{ data : &"Listening to Soilwork while writing this.", next : Rc::new(|x| f64::from(x)*0.5f64)}); + let m = m.bind(|x : f64| -> FreeTest<_,_> { if x < 0.0 { - FreeTest::Pure(x.abs().floor() as u32) + FreeTest::Pure(x.abs().floor()) } else { - FreeTest::lift_f(TestFunctor{data : &"Now it's Little Big.", next : Rc::new(move |y| (y as u32) + (x.ceil() as u32))}) + FreeTest::lift_f(TestFunctor{data : &"Now it's Little Big.", next : Rc::new(move |y| f64::from(y) + x.ceil())}) }}); match m{ FreeTest::Free(m) => { assert_eq!(m.data, &"Listening to Soilwork while writing this."); match (m.next)(-3){ - FreeTest::Pure(v) => assert_eq!(v, 1), + FreeTest::Pure(v) => assert_nearly_equal!(v, 1f64, f64::EPSILON), FreeTest::Free(_) => unreachable!(), } match (m.next)(3){ @@ -68,14 +77,12 @@ fn test_lifetime_multiple_generics_bind(){ FreeTest::Free(v) => { assert_eq!(v.data, &"Now it's Little Big."); match (v.next)(5) { - FreeTest::Pure(v) => { - assert_eq!(v, 7) - }, + FreeTest::Pure(v) => assert_nearly_equal!(v, 7f64, f64::EPSILON), FreeTest::Free(_) => unreachable!(), } }, } }, - _ => unreachable!() + FreeTest::Pure(_) => unreachable!() } }
\ No newline at end of file diff --git a/tests/trivial.rs b/tests/trivial.rs index 5468fe4..5bc1e91 100644 --- a/tests/trivial.rs +++ b/tests/trivial.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! A trivial test functor. Not holding any data, so this is basically just a linked list of free-nodes. use higher_free_macro::free; use higher::{Functor, Bind, Apply}; @@ -24,16 +26,16 @@ fn test_trivial_functor() { TrivialFunctor(f) => { match f{ TrivialFreeMonad::Pure(x) => assert_eq!(x, 37*6), - _ => unreachable!() + TrivialFreeMonad::Free(_) => unreachable!() } } } }, - _ => unreachable!() + TrivialFreeMonad::Pure(_) => unreachable!() } } } } - _ => unreachable!() + TrivialFreeMonad::Pure(_) => unreachable!() } }
\ No newline at end of file diff --git a/tests/trivial_lifetime.rs b/tests/trivial_lifetime.rs index fe05234..4f7d094 100644 --- a/tests/trivial_lifetime.rs +++ b/tests/trivial_lifetime.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! Tests if a trivial functor, which's lifetime depends on the mapping function, works. use std::rc::Rc; use higher::{Functor, Bind}; @@ -22,8 +24,8 @@ free!(<'a>, FreeTriv<'a,A,B>, TrivWithLifetime<'a,FreeTriv<'a,A,B>,B>); #[test] fn test_trivial_with_lifetime(){ - let f = FreeTriv::lift_f(TrivWithLifetime{next : Rc::new(|x : i32| x.abs() as u32)}); - let f = f.bind(|x| FreeTriv::Pure(x)); + let f = FreeTriv::lift_f(TrivWithLifetime{next : Rc::new(i32::unsigned_abs)}); + let f = f.bind(FreeTriv::Pure); match f { FreeTriv::Free(f) => { let n = (f.next)(-4); @@ -32,6 +34,6 @@ fn test_trivial_with_lifetime(){ FreeTriv::Free(_) => unreachable!(), } }, - _ => unreachable!() + FreeTriv::Pure(_) => unreachable!() } }
\ No newline at end of file diff --git a/tests/vector.rs b/tests/vector.rs index 74cc913..2b2f4b2 100644 --- a/tests/vector.rs +++ b/tests/vector.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! Tests if creating a Free Monad for a Vec works. Not sure if this is useful in any way. //! It is a nice illustration that Free Monads are tree-like though. @@ -6,117 +8,64 @@ use higher::{Functor, Bind, Apply}; free!(FreeVec<A>, Vec<FreeVec<A>>); + +//just to appease clippy without disabling the lint.... +macro_rules! assert_nearly_equal { + ($a:expr, $b:expr, $c:expr) => { + assert!((($a)-($b)).abs() < $c) + }; +} + #[test] fn test_vector(){ - let fv = FreeVec::lift_f(vec![2,3,4]); - let fv = fv.fmap(|x| x*2); - let fv = fv.bind(|x| if x%3 == 0 {FreeVec::Pure(x)} else {FreeVec::lift_f(vec![x,x+1])}); - let f = FreeVec::lift_f(vec![(|x| (x as f32) / 3.0) as fn(u32)->f32, (|x| (x+2) as f32) as fn(u32)->f32]); - let r = fv.apply(f.fmap(Into::into)); - match r { + let free_monad = FreeVec::lift_f(vec![2,3,4]); + let free_monad_after_fmap = free_monad.fmap(|x| x*2); + let free_monad_after_bind = free_monad_after_fmap.bind(|x| if x%3 == 0 {FreeVec::Pure(x)} else {FreeVec::lift_f(vec![x,x+1])}); + let functions = FreeVec::lift_f(vec![(|x| f64::from(x) / 3.0) as fn(u32)->f64, (|x| f64::from(x+2)) as fn(u32)->f64]); + let free_monad_after_apply = free_monad_after_bind.apply(functions.fmap(Into::into)); + match free_monad_after_apply { FreeVec::Free(v) => { match &**v{ - [a,b] => { - match a { - FreeVec::Free(v) => { - match &***v { - [a,b,c] => { - match a { - FreeVec::Free(v) => { - match &***v { - [a,b] => { - match a{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(4.0f32/3.0f32, *v)} - } - match b{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(5.0f32/3.0f32, *v)} - } - }, - _ => unreachable!() - } - } - FreeVec::Pure(_) => unreachable!(), - } - match b { - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => assert_eq!(2.0f32, *v), - } - match c { - FreeVec::Free(v) => { - match &***v { - [a,b] => { - match a{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(8.0f32/3.0f32, *v)} - } - match b{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(3.0f32, *v)} - } - }, - _ => unreachable!() - } - } - FreeVec::Pure(_) => unreachable!(), - } + [FreeVec::Free(left), FreeVec::Free(right)] => { + match &***left { + [FreeVec::Free(left), FreeVec::Pure(middle), FreeVec::Free(right)] => { + match &***left { + [FreeVec::Pure(left), FreeVec::Pure(right)] => { + assert_nearly_equal!(4.0f64/3.0f64, *left, f64::EPSILON); + assert_nearly_equal!(5.0f64/3.0f64, *right, f64::EPSILON); + }, + _ => unreachable!() + } + assert_nearly_equal!(2.0f64, *middle, f64::EPSILON); + match &***right { + [FreeVec::Pure(left),FreeVec::Pure(right)] => { + assert_nearly_equal!(8.0f64/3.0f64, *left, f64::EPSILON); + assert_nearly_equal!(3.0f64, *right, f64::EPSILON); }, _ => unreachable!() } - } - FreeVec::Pure(_) => unreachable!() + }, + _ => unreachable!() } - - match b { - FreeVec::Free(v) => { - match &***v { - [a,b,c] => { - match a { - FreeVec::Free(v) => { - match &***v { - [a,b] => { - match a{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(6.0f32, *v)} - } - match b{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(7.0f32, *v)} - } - }, - _ => unreachable!() - } - } - FreeVec::Pure(_) => unreachable!(), - } - match b { - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => assert_eq!(8.0f32, *v), - } - match c { - FreeVec::Free(v) => { - match &***v { - [a,b] => { - match a{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(10.0f32, *v)} - } - match b{ - FreeVec::Free(_) => unreachable!(), - FreeVec::Pure(v) => {assert_eq!(11.0f32, *v)} - } - }, - _ => unreachable!() - } - } - FreeVec::Pure(_) => unreachable!(), - } + match &***right { + [FreeVec::Free(left),FreeVec::Pure(middle),FreeVec::Free(right)] => { + match &***left { + [FreeVec::Pure(left),FreeVec::Pure(right)] => { + assert_nearly_equal!(6.0f64, *left, f64::EPSILON); + assert_nearly_equal!(7.0f64, *right, f64::EPSILON); + }, + _ => unreachable!() + } + assert_nearly_equal!(8.0f64, *middle, f64::EPSILON); + match &***right { + [FreeVec::Pure(left),FreeVec::Pure(right)] => { + assert_nearly_equal!(10.0f64, *left, f64::EPSILON); + assert_nearly_equal!(11.0f64, *right, f64::EPSILON); }, _ => unreachable!() } - } - FreeVec::Pure(_) => unreachable!() + }, + _ => unreachable!() } }, _ => unreachable!() diff --git a/tests/with_lifetimes.rs b/tests/with_lifetimes.rs index 2e87878..697fc74 100644 --- a/tests/with_lifetimes.rs +++ b/tests/with_lifetimes.rs @@ -1,3 +1,5 @@ +#![deny(clippy::pedantic)] +#![deny(clippy::all)] //! Test for the case that the Functor the Free Monad is based on has lifetime parameters that do not depend on the //! lifetime of the mapping function in the Functor implementation. |
