diff options
| author | Andreas Grois <andi@grois.info> | 2023-04-02 21:59:22 +0200 |
|---|---|---|
| committer | Andreas Grois <andi@grois.info> | 2023-04-02 21:59:22 +0200 |
| commit | cebdd3be32d50be379663e92d4428e6bba19ba51 (patch) | |
| tree | 8c5b57f2e2ed3c089e0546fdb95ef8c2082d1952 | |
| parent | 418f514fc46f45ae2901753e3398adb33664bed9 (diff) | |
Run cargo fmt
I think readability was better before that...
| -rw-r--r-- | examples/text-adventure/data.rs | 48 | ||||
| -rw-r--r-- | examples/text-adventure/dsl.rs | 94 | ||||
| -rw-r--r-- | examples/text-adventure/logic.rs | 120 | ||||
| -rw-r--r-- | examples/text-adventure/main.rs | 14 | ||||
| -rw-r--r-- | examples/text-adventure/side_effects.rs | 42 | ||||
| -rw-r--r-- | src/lib.rs | 414 | ||||
| -rw-r--r-- | tests/multiple_generics.rs | 86 | ||||
| -rw-r--r-- | tests/multiple_generics_lifetime.rs | 70 | ||||
| -rw-r--r-- | tests/trivial.rs | 40 | ||||
| -rw-r--r-- | tests/trivial_lifetime.rs | 33 | ||||
| -rw-r--r-- | tests/vector.rs | 106 | ||||
| -rw-r--r-- | tests/with_lifetimes.rs | 49 |
12 files changed, 623 insertions, 493 deletions
diff --git a/examples/text-adventure/data.rs b/examples/text-adventure/data.rs index 3e9c49e..bf78977 100644 --- a/examples/text-adventure/data.rs +++ b/examples/text-adventure/data.rs @@ -1,13 +1,13 @@ #[derive(Clone, Copy)] -pub enum Speaker{ +pub enum Speaker { Partner, DeliLady, Cashier, } -impl Speaker{ - pub fn text_description(self)->&'static str{ - match self{ +impl Speaker { + pub fn text_description(self) -> &'static str { + match self { Speaker::Partner => "Your partner", Speaker::DeliLady => "The lady behind the deli counter", Speaker::Cashier => "The cashier", @@ -16,7 +16,7 @@ impl Speaker{ } #[derive(Clone, Copy)] -pub enum Mood{ +pub enum Mood { Friendly, Confused, Happy, @@ -25,9 +25,9 @@ pub enum Mood{ Apologetic, } -impl Mood{ - pub fn text_description(self)->&'static str{ - match self{ +impl Mood { + pub fn text_description(self) -> &'static str { + match self { Mood::Friendly => "a friendly expression", Mood::Confused => "a confused expression", Mood::Happy => "a happy expression", @@ -39,7 +39,7 @@ impl Mood{ } #[derive(Clone, Copy, PartialEq, Eq)] -pub enum Location{ +pub enum Location { Entrance, Deli, Checkout, @@ -47,9 +47,9 @@ pub enum Location{ Shelves, } -impl Location{ +impl Location { //Used if we want to present the location in text. If we were writing a visual novel, we would offer another function that returns the respective assets. - pub fn get_text_description(self)->&'static str{ + pub fn get_text_description(self) -> &'static str { match self { Location::Entrance => "You are at the entrance area of the super market. Behind you is the parking lot, in front the inviting automated doors of the entrance. Your partner is here with you.", Location::Deli => "This is the area with the deli counter. There is a lady wearing a hair protector and plastic gloves standing behind the presentation tray.", @@ -60,8 +60,8 @@ impl Location{ } } -#[derive(Copy,Clone,PartialEq,Eq)] -pub enum Item{ +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Item { //refrigerators Milk, Yoghurt, @@ -81,7 +81,7 @@ pub enum Item{ } impl Item { - fn price(self) -> usize { + fn price(self) -> usize { match self { Item::SausageRoll | Item::FishSandwich | Item::Shots => 300, Item::Pickles | Item::Pulp => 250, @@ -110,7 +110,7 @@ impl Item { } } -impl Location{ +impl Location { pub fn items(self) -> Vec<Item> { match self { Location::Entrance => Vec::default(), @@ -126,34 +126,34 @@ impl Location{ //I left it as Clone intentionally, to illustrate how one can work around the limitation of it not being Copy. #[derive(Clone, Default)] pub struct Inventory { - pub items : Vec<Item>, + pub items: Vec<Item>, } -impl Inventory{ - pub fn has_item_from_room(&self, room : Location) -> bool { +impl Inventory { + pub fn has_item_from_room(&self, room: Location) -> bool { let items_from_room = room.items(); self.items.iter().any(|i| items_from_room.contains(i)) } - pub fn try_add(self, item : Item) -> Result<Self, Self>{ + pub fn try_add(self, item: Item) -> Result<Self, Self> { 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}) + Ok(Inventory { items }) } else { Err(self) } } - pub fn try_remove(mut self, item : Item) -> Result<Self, Self>{ + pub fn try_remove(mut self, item: Item) -> Result<Self, Self> { let idx = self.items.iter().position(|i| *i == item); match idx { Some(idx) => { self.items.swap_remove(idx); Ok(self) - }, + } None => Err(self), } } - pub fn get_money() -> &'static str{ + pub fn get_money() -> &'static str { "€10.00" //It doesn't change. Can as well be a constant. } pub fn total_price(&self) -> usize { @@ -162,4 +162,4 @@ impl Inventory{ pub fn can_afford(&self) -> bool { self.total_price() <= 1000_usize } -}
\ No newline at end of file +} diff --git a/examples/text-adventure/dsl.rs b/examples/text-adventure/dsl.rs index c448f04..73b6ad4 100644 --- a/examples/text-adventure/dsl.rs +++ b/examples/text-adventure/dsl.rs @@ -1,63 +1,95 @@ //This module defines the domain specific language. -use std::{rc::Rc, borrow::Cow}; +use std::{borrow::Cow, rc::Rc}; +use super::data::{Location, Mood, Speaker}; +use higher::Functor; use higher_free_macro::free; use std::convert::identity; -use higher::Functor; -use super::data::{Location, Mood, Speaker}; #[derive(Clone)] -pub enum SausageRoll<'a, 's,A>{ - SayDialogueLine{ +pub enum SausageRoll<'a, 's, A> { + SayDialogueLine { speaker: Speaker, - text : Cow<'s, str>, //In a real project I would just make this a String. Here it's a reference to show off lifetime support. - mood : Mood, - next : A + text: Cow<'s, str>, //In a real project I would just make this a String. Here it's a reference to show off lifetime support. + mood: Mood, + next: A, }, - GivePlayerOptions{ - options : Vec<&'s str>, - next : Rc<dyn Fn(usize)-> A + 'a> //let's just assume that the interpreter validates input. + GivePlayerOptions { + options: Vec<&'s str>, + next: Rc<dyn Fn(usize) -> A + 'a>, //let's just assume that the interpreter validates input. }, - PresentLocation{ - location : Location, - next : A, + PresentLocation { + location: Location, + next: A, + }, + Exposition { + text: &'s str, + next: A, }, - Exposition{ - text : &'s str, - next : A, - } } -impl<'a,'s, A : 'a> Functor<'a,A> for SausageRoll<'a,'s,A>{ - type Target<T> = SausageRoll<'a,'s,T>; +impl<'a, 's, A: 'a> Functor<'a, A> for SausageRoll<'a, 's, A> { + type Target<T> = SausageRoll<'a, 's, T>; fn fmap<B, F>(self, f: F) -> Self::Target<B> where - F: Fn(A) -> B + 'a { + F: Fn(A) -> B + 'a, + { match self { - SausageRoll::SayDialogueLine { speaker, text, mood, next } => SausageRoll::SayDialogueLine { speaker, text, mood, next: f(next) }, - SausageRoll::GivePlayerOptions { options, next } => SausageRoll::GivePlayerOptions { options, next: Rc::new(move |x| f(next(x))) }, - SausageRoll::PresentLocation { location, next } => SausageRoll::PresentLocation { location, next: f(next) }, - SausageRoll::Exposition { text, next } => SausageRoll::Exposition { text, next: f(next) }, + SausageRoll::SayDialogueLine { + speaker, + text, + mood, + next, + } => SausageRoll::SayDialogueLine { + speaker, + text, + mood, + next: f(next), + }, + SausageRoll::GivePlayerOptions { options, next } => SausageRoll::GivePlayerOptions { + options, + next: Rc::new(move |x| f(next(x))), + }, + SausageRoll::PresentLocation { location, next } => SausageRoll::PresentLocation { + location, + next: f(next), + }, + SausageRoll::Exposition { text, next } => SausageRoll::Exposition { + text, + next: f(next), + }, } } } free!(<'a>, pub FreeSausageRoll<'a,'s,A>, SausageRoll<'a, 's, FreeSausageRoll<'a,'s,A>>); -pub fn say_dialogue_line<'a,'s:'a>(speaker : Speaker, text : Cow<'s,str>, mood : Mood) -> FreeSausageRoll<'a, 's, ()>{ - FreeSausageRoll::lift_f(SausageRoll::SayDialogueLine { speaker, text, mood, next: () }) +pub fn say_dialogue_line<'a, 's: 'a>( + speaker: Speaker, + text: Cow<'s, str>, + mood: Mood, +) -> FreeSausageRoll<'a, 's, ()> { + FreeSausageRoll::lift_f(SausageRoll::SayDialogueLine { + speaker, + text, + mood, + next: (), + }) } -pub fn give_player_options<'a,'s:'a>(options : Vec<&'s str>) -> FreeSausageRoll<'a,'s, usize>{ - FreeSausageRoll::lift_f(SausageRoll::GivePlayerOptions { options, next: Rc::new(identity) }) +pub fn give_player_options<'a, 's: 'a>(options: Vec<&'s str>) -> FreeSausageRoll<'a, 's, usize> { + FreeSausageRoll::lift_f(SausageRoll::GivePlayerOptions { + options, + next: Rc::new(identity), + }) } -pub fn present_location<'a, 's:'a>(location : Location) -> FreeSausageRoll<'a,'s, ()>{ +pub fn present_location<'a, 's: 'a>(location: Location) -> FreeSausageRoll<'a, 's, ()> { FreeSausageRoll::lift_f(SausageRoll::PresentLocation { location, next: () }) } -pub fn exposition<'a,'s:'a>(text : &'s str) -> FreeSausageRoll<'a,'s,()>{ +pub fn exposition<'a, 's: 'a>(text: &'s str) -> FreeSausageRoll<'a, 's, ()> { FreeSausageRoll::lift_f(SausageRoll::Exposition { text, next: () }) } diff --git a/examples/text-adventure/logic.rs b/examples/text-adventure/logic.rs index d884598..3f05555 100644 --- a/examples/text-adventure/logic.rs +++ b/examples/text-adventure/logic.rs @@ -1,11 +1,12 @@ //! This module does nothing. It just creates the game's high level flow encoded as Free Monad. - use std::borrow::Cow; -use higher::{run, Functor, Pure, Bind}; use super::data::{Inventory, Item, Location, Mood, Speaker}; -use super::dsl::{FreeSausageRoll, exposition, give_player_options, present_location, say_dialogue_line}; +use super::dsl::{ + exposition, give_player_options, present_location, say_dialogue_line, FreeSausageRoll, +}; +use higher::{run, Bind, Functor, Pure}; //Haskell has a when function, and it's nice. Sooo, copy that. macro_rules! when { @@ -18,9 +19,8 @@ macro_rules! when { }; } - -pub fn game<'a,'s : 'a>() -> FreeSausageRoll<'a, 's, ()>{ - run!{ +pub fn game<'a, 's: 'a>() -> FreeSausageRoll<'a, 's, ()> { + run! { c <= intro(); when!{c => { //handle_rooms is the main game loop: Go from room to room. @@ -31,8 +31,8 @@ pub fn game<'a,'s : 'a>() -> FreeSausageRoll<'a, 's, ()>{ } } -fn intro<'a,'s:'a>() -> FreeSausageRoll<'a, 's, bool>{ - run!{ +fn intro<'a, 's: 'a>() -> FreeSausageRoll<'a, 's, bool> { + run! { present_location(Location::Entrance); say_dialogue_line(Speaker::Partner, Cow::from("Would you be so kind as to quickly grab me a sausage roll from the supermarket? With pickle if possible?"), Mood::Friendly); say_dialogue_line(Speaker::Partner, Cow::from("I'd meanwhile go over to the pharmacy, and buy some pills against headache."), Mood::Friendly); @@ -47,24 +47,24 @@ fn intro<'a,'s:'a>() -> FreeSausageRoll<'a, 's, bool>{ } } -fn ending<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ()>{ +fn ending<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, ()> { if inventory.items.contains(&Item::SausageRoll) { - if inventory.items.contains(&Item::Pickles){ - run!{ + if inventory.items.contains(&Item::Pickles) { + run! { say_dialogue_line(Speaker::Partner, Cow::from("Wait, seriously? You bought a glass of pickles and a sausage roll without pickle?"), Mood::Confused); exposition("You explain that the deli counter had run out of pickles."); say_dialogue_line(Speaker::Partner, Cow::from("Well, that's a creative solution."), Mood::Amused); say_dialogue_line(Speaker::Partner, Cow::from("Thanks a lot, let's move on."), Mood::Happy) } } else { - run!{ + run! { say_dialogue_line(Speaker::Partner, Cow::from("Thanks for the sausage roll, but there are no pickles in it?"), Mood::Annoyed); exposition("You explain that the deli counter had run out of pickles."); say_dialogue_line(Speaker::Partner, Cow::from("Well, that can't be helped then. Thanks a lot, let's move on."), Mood::Happy) } } } else { - run!{ + 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.is_empty() => { say_dialogue_line(Speaker::Partner, Cow::from("Also, why did you buy all that other stuff?"), Mood::Annoyed) @@ -74,8 +74,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!{ +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 { FreeSausageRoll::pure(c) @@ -86,8 +89,11 @@ fn handle_rooms<'a,'s:'a>(room: Location, inventory : Inventory) -> FreeSausageR } } -fn handle_room<'a,'s:'a>(room : Location, inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)> { - run!{ +fn handle_room<'a, 's: 'a>( + room: Location, + inventory: Inventory, +) -> FreeSausageRoll<'a, 's, (Location, Inventory)> { + run! { present_location(room); match room { Location::Refrigerators => handle_refrigerators(inventory.clone()), @@ -97,10 +103,12 @@ fn handle_room<'a,'s:'a>(room : Location, inventory : Inventory) -> FreeSausageR Location::Entrance => unreachable!(), //if we are at the entrance, we won. } } -} +} -fn handle_refrigerators<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{ - run!{ +fn handle_refrigerators<'a, 's: 'a>( + inventory: Inventory, +) -> FreeSausageRoll<'a, 's, (Location, Inventory)> { + run! { c <= { let options = if inventory.has_item_from_room(Location::Refrigerators) { vec!["Move on to the Shelves.", "Move to the deli counter.", "Check Inventory", "Take an item", "Return an item"] @@ -112,7 +120,7 @@ fn handle_refrigerators<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a match c { 0 => { FreeSausageRoll::pure((Location::Shelves, inventory.clone())) }, 1 => { FreeSausageRoll::pure((Location::Deli, inventory.clone()))}, - 2 => { + 2 => { let inventory = inventory.clone(); run!{ check_inventory(inventory.clone()); @@ -132,9 +140,11 @@ fn handle_refrigerators<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a } } -fn handle_shelves<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{ +fn handle_shelves<'a, 's: 'a>( + inventory: Inventory, +) -> FreeSausageRoll<'a, 's, (Location, Inventory)> { //this is rather similar to refrigerators. Just different items. - run!{ + run! { c <= { let options = if inventory.has_item_from_room(Location::Shelves) { vec!["Move on to the Refrigerators.", "Move to the deli counter.", "Move to the checkout.", "Check Inventory","Take an item", "Return an item"] @@ -167,8 +177,8 @@ fn handle_shelves<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, } } -fn handle_deli<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{ - run!{ +fn handle_deli<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)> { + run! { c <= give_player_options(vec!["Move on to refrigerators.", "Move on to shelves.", "Check Inventory", "Talk to the lady behind the counter"]); match c{ 0 => FreeSausageRoll::pure((Location::Refrigerators, inventory.clone())), @@ -191,8 +201,10 @@ fn handle_deli<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Loca } } -fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, (Location, Inventory)>{ - run!{ +fn handle_checkout<'a, 's: 'a>( + inventory: Inventory, +) -> FreeSausageRoll<'a, 's, (Location, Inventory)> { + run! { c <= { let options = if inventory.has_item_from_room(Location::Checkout) { vec!["Move back to the shelves.", "Pay for your stuff and leave.", "Check Inventory", "Take an item", "Return an item"] @@ -203,7 +215,7 @@ fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ( }; match c { 0 => { FreeSausageRoll::pure((Location::Shelves, inventory.clone())) }, - 1 => { + 1 => { run!{ r <= try_pay(inventory.clone()); match r { @@ -237,11 +249,14 @@ fn handle_checkout<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a, 's, ( } } -fn try_take_item<'a, 's: 'a>(inventory : Inventory, options : Vec<Item>) -> FreeSausageRoll<'a, 's,Inventory>{ +fn try_take_item<'a, 's: 'a>( + inventory: Inventory, + options: Vec<Item>, +) -> FreeSausageRoll<'a, 's, Inventory> { //here we run into an "interesting" issue with Rust's ownership and do-notation. //We would like to capture inventory and use it in bind-notation, but that doesn't work (except in the first 2 lines), because Inventory isn't Copy. //This leaves us with a couple of options: We can either pass it through by repeated cloning (done here), or leave do-notation before capturing it. - run!{ + run! { i <= exposition("You look around and these items nearby catch your attention.").fmap(move |_| (inventory.clone(), options.clone())); o <= give_player_options(i.1.iter().map(|o| o.description()).chain(std::iter::once("Cancel")).collect()).fmap(move |c| (c, i.0.clone(), i.1.clone())); { @@ -269,11 +284,19 @@ fn try_take_item<'a, 's: 'a>(inventory : Inventory, options : Vec<Item>) -> Free } } -fn return_item<'a,'s:'a>(inventory : Inventory, room : Location) -> FreeSausageRoll<'a,'s, Inventory>{ +fn return_item<'a, 's: 'a>( + inventory: Inventory, + room: Location, +) -> FreeSausageRoll<'a, 's, Inventory> { //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)).copied().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! { @@ -281,26 +304,30 @@ fn return_item<'a,'s:'a>(inventory : Inventory, room : Location) -> FreeSausageR give_player_options(carried_items_from_here.iter().map(|o| o.description()).chain(std::iter::once("Cancel")).collect()) }; chosen.bind(move |c| { - match carried_items_from_here2.get(c).ok_or_else(|| inventory.clone()).and_then(|i| inventory.clone().try_remove(*i)) { + match carried_items_from_here2 + .get(c) + .ok_or_else(|| inventory.clone()) + .and_then(|i| inventory.clone().try_remove(*i)) + { Ok(i) => { - run!{ + run! { exposition("You put back the item."); yield i.clone() } - }, + } Err(i) => { run! { exposition("You decided to not return an item."); //good enough. We filtered for valid items beforehand. yield i.clone() } - }, + } } }) } -fn check_inventory<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s, ()>{ +fn check_inventory<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, ()> { let c = inventory.items.len(); - run!{ + run! { exposition("You look at the items you carry. You are holding:"); list_inventory_items(inventory.clone(),0); if c < 2 { @@ -314,9 +341,12 @@ fn check_inventory<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s, ( } } -fn list_inventory_items<'a,'s:'a>(inventory : Inventory, index : usize) -> FreeSausageRoll<'a,'s, ()>{ +fn list_inventory_items<'a, 's: 'a>( + inventory: Inventory, + index: usize, +) -> FreeSausageRoll<'a, 's, ()> { if index < inventory.items.len() { - run!{ + run! { exposition(inventory.items[index].description()); list_inventory_items(inventory.clone(), index+1) } @@ -325,7 +355,7 @@ fn list_inventory_items<'a,'s:'a>(inventory : Inventory, index : usize) -> FreeS } } -fn talk_to_deli_lady<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Inventory>{ +fn talk_to_deli_lady<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, Inventory> { run!{ exposition("You greet the lady at the deli counter."); say_dialogue_line(Speaker::DeliLady, Cow::from("Hi! How can I help you, dear?"), Mood::Friendly); @@ -333,7 +363,7 @@ fn talk_to_deli_lady<'a,'s:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,I }.bind(move |_| deli_lady_loop(inventory.clone())) } -fn deli_lady_loop<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Inventory>{ +fn deli_lady_loop<'a, 's: 'a>(inventory: Inventory) -> FreeSausageRoll<'a, 's, Inventory> { let has_deli_item = inventory.has_item_from_room(Location::Deli); let c = run! { if has_deli_item { @@ -395,7 +425,9 @@ fn deli_lady_loop<'a, 's: 'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,In }) } -fn try_pay<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Result<Inventory,Inventory>>{ +fn try_pay<'a, 's: 'a>( + inventory: Inventory, +) -> FreeSausageRoll<'a, 's, Result<Inventory, Inventory>> { let total_price = inventory.total_price(); let can_afford = inventory.can_afford(); run!{ @@ -419,4 +451,4 @@ fn try_pay<'a, 's:'a>(inventory : Inventory) -> FreeSausageRoll<'a,'s,Result<Inv } } }.fmap(move |e| e.map(|_| inventory.clone()).map_err(|_| inventory.clone())) -}
\ No newline at end of file +} diff --git a/examples/text-adventure/main.rs b/examples/text-adventure/main.rs index 1d538cc..ff9b8dd 100644 --- a/examples/text-adventure/main.rs +++ b/examples/text-adventure/main.rs @@ -1,27 +1,27 @@ #![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. -//! +//! //! The code of this example contains a few peculiarities, to highlight features of and issues with the current //! Free Monad code. //! For instance, it intentionally does not have `Copy` implemented on the player's inventory, to illustrate how //! one can work around a limitation in the current run!{} macro version. //! Another thing that is not really that useful in practice is that all strings that are hardcoded are references //! instead of owned copies. This is just to illustrate that lifetimes are supported too. -//! +//! //! 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 [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. -//! +//! //! 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. @@ -33,7 +33,7 @@ mod side_effects; fn main() -> std::io::Result<()> { //Let's build the game logic. As a data structure. let game = logic::game(); - + //And now let's do something with it. side_effects::run(game) -}
\ No newline at end of file +} diff --git a/examples/text-adventure/side_effects.rs b/examples/text-adventure/side_effects.rs index dd911dd..379ed5d 100644 --- a/examples/text-adventure/side_effects.rs +++ b/examples/text-adventure/side_effects.rs @@ -2,22 +2,30 @@ use crate::dsl::FreeSausageRoll; -pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result<()>{ +pub fn run<'a, 's: 'a>(mut game: FreeSausageRoll<'a, 's, ()>) -> std::io::Result<()> { //this function doesn't know who it is, or why it is here. It only knows it must deal. //Deal with the few commands in the eDSL and nothing more. //This would be easier to write recursively. However, in an actual project this might run for quite some time. //Since we operate on the stack, let's rather be safe than sorry, and use a loop instead of recursion therefore. - while let FreeSausageRoll::Free(command) = game { + while let FreeSausageRoll::Free(command) = game { game = match *command { - crate::dsl::SausageRoll::SayDialogueLine { speaker, text, mood, next } => { - println!("{} says: \"{text}\" with {} on their face.", speaker.text_description(), mood.text_description()); + crate::dsl::SausageRoll::SayDialogueLine { + speaker, + text, + mood, + next, + } => { + println!( + "{} says: \"{text}\" with {} on their face.", + speaker.text_description(), + mood.text_description() + ); next - }, - crate::dsl::SausageRoll::GivePlayerOptions { options, next } => - { + } + crate::dsl::SausageRoll::GivePlayerOptions { options, next } => { println!("Your options are:"); - for (id, option) in options.iter().enumerate().map(|(i,o)| (i+1,o)){ + for (id, option) in options.iter().enumerate().map(|(i, o)| (i + 1, o)) { println!("{id}: {option}"); } @@ -26,23 +34,27 @@ pub fn run<'a, 's:'a>(mut game : FreeSausageRoll<'a, 's, ()>) -> std::io::Result while { input.clear(); std::io::stdin().read_line(&mut input)?; - chosen = input.trim().parse().ok().filter(|o : &usize| *o > 0 && *o <= options.len()); + chosen = input + .trim() + .parse() + .ok() + .filter(|o: &usize| *o > 0 && *o <= options.len()); chosen.is_none() } { println!("Invalid choice. Please select one of the options given above."); } println!(); - next(chosen.unwrap()-1) - }, + next(chosen.unwrap() - 1) + } crate::dsl::SausageRoll::PresentLocation { location, next } => { println!("{}", location.get_text_description()); next - }, + } crate::dsl::SausageRoll::Exposition { text, next } => { println!("{text}"); next - }, + } }; - }; + } std::io::Result::Ok(()) -}
\ No newline at end of file +} @@ -1,51 +1,51 @@ #![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. -//! +//! //! # What is a Free Monad? //! A Free Monad is the left-adjoint to the Forget-Functor from the category of Monads into the category of Endofunctors. -//! +//! //! From a programmer's perspective, however, it is a nifty way to create a [`Monad`][higher::Monad], that is "based" on a given [`Functor`][higher::Functor] //! and does not impose any additional structure beyond the [Monad Laws](https://wiki.haskell.org/Monad_laws). -//! +//! //! The structure of the Free [`Monad`][higher::Monad] is defined by the underlying [`Functor`][higher::Functor]. //! For instance, if the underlying [`Functor`][higher::Functor] is a [`Vec`], the corresponding Free [`Monad`][higher::Monad] will be a linked tree. //! If the underlying [`Functor`][higher::Functor] is an [`Option`], the corresponding Free [`Monad`][higher::Monad] is a linked list. //! And so on, and so forth. -//! +//! //! There are many use cases for such a data structure, the most well known one is the creation of embedded //! [Domain Specific Languages](https://en.wikipedia.org/wiki/Domain-specific_language) (eDSLs). //! Going into detail would go beyond the scope of this documentation, however. Please check out Nikolay Yakimov's //! [Introduction to Free Monads](https://serokell.io/blog/introduction-to-free-monads) for that. -//! +//! //! There is also a [blog post about the development of this macro](https://www.grois.info/posts/2023-03/2023-03-11-adventures-with-free-monads-and-higher.xhtml), //! that presents a simple (but inexact) mental picture //! (by means of actual [pictures](https://www.grois.info/posts/2023-03/2023-03-11-adventures-with-free-monads-and-higher.xhtml#ugly_drawings)) //! of how the different [`Monad`][higher::Monad] operations (bind, fmap, pure, apply) work on Free Monads. -//! +//! //! # How to use the macro? -//! +//! //! For details, please see the documentation of the [free] macro. //! In short, the syntax is either `free!(FreeMonadTypeName<'a, A>, FunctorItsBasedOn<FreeMonadTypeName<'a, A>>)`, //! or, if the lifetime of the Free Monad depends on the lifetime of the function passed to the Functor's fmap function, //! `free!(<'a>, FreeMonadTypeName<'a,A>, FunctorItsBasedOn<'a,FreeMonadTypeName<'a,A>>)`, where `'a` is the affected lifetime. -//! +//! //! # Examples //! The project's repository contains a folder named "examples", which at the moment contains a tiny text adventure that shows how such a game //! could be implemented with Free Monads. The example highlights both, features and (current) limitations of Free Monads in Rust. -//! +//! //! In addition, there is the "tests" folder, which contains integration tests, that show the syntax of the `free!()` macro in action. -//! +//! //! # Why a Macro? //! Until [non-lifetime binders](https://github.com/rust-lang/rust/issues/108185) become stable, this seems to be the easiest way. //! In generic code, the type signature would be `enum Free<A,F> where F : Functor<Free<A,F>>`. If one now wants to implement the [`Functor`][higher::Functor] //! trait for this, it is not really possible to express the `Target<T> = Free<A,F::Target<Free<A,F::Target<...>>>>` generic associated type. -//! +//! //! See the [blog post about this crate](https://www.grois.info/posts/2023-03/2023-03-11-adventures-with-free-monads-and-higher.xhtml) //! for a more detailed explanation. -//! +//! //! # A word of warning: //! This crate should be considered a proof-of-concept. Its memory complexity is horrendous, and the performance of the Free Monad's [`Apply`][higher::Apply] //! implementation can only be described as abysmal due to its reliance on deep copies. @@ -57,42 +57,42 @@ pub extern crate higher; /// The macro that generates a Free [`Monad`][higher::Monad] type for a given [`Functor`][higher::Functor]. -/// +/// /// To declare a Free [`Monad`][higher::Monad] over a [`Functor`][higher::Functor] named `Funky<A>`, the syntax would be `free!(FreeFunky<A>, Funky<FreeFunky<A>>)`. /// This declares an enum named `FreeFunky<A>`, and implements all traits needed for it to be a [`Monad`][higher::Monad]. -/// +/// /// # Restrictions /// It is currently not supported to create a Free Monad for a Functor that does not implement [`Clone`]. This is because it is in general not /// possible to implement [`Apply`][higher::Apply] for a non-cloneable Free Monad, and because of how Rust resolves trait bounds in recursive types. -/// +/// /// In addition, for the result to actually be a [`Monad`][higher::Monad], the `Pure` type (the type the Free Monad is generic over) needs to support [`Clone`] /// too. This is again because of the requirement of [`Apply`][higher::Apply], which in turn is a requirement of [`Monad`][higher::Monad]. However, -/// it is typically not necessary to have a fully fledged [`Monad`][higher::Monad]. In most use cases, it's enough to have +/// it is typically not necessary to have a fully fledged [`Monad`][higher::Monad]. In most use cases, it's enough to have /// [`Functor`][higher::Functor] + [`Bind`][higher::Bind] + [`Pure`][higher::Pure]. -/// +/// /// The Free Monad type is implemented recursively. It is therefore akin to a linked tree, with all the respective performance implications. -/// +/// /// Furthermore, the implementation of [`Apply`][higher::Apply] creates a potentially high number of deep copies of the `self` parameter. -/// It should therefore be avoided, unless one really needs its +/// It should therefore be avoided, unless one really needs its /// [tree-merging behaviour](https://www.grois.info/posts/2023-03/2023-03-11-adventures-with-free-monads-and-higher.xhtml#ugly_apply_drawing). -/// +/// /// # Usage /// As stated above, the syntax to create a Free Monad is usually to call the macro with the desired Free Monad type as first, /// and the [`Functor`][higher::Functor] it should be based on as second parameter. -/// +/// /// For example, a Free Monad based on [`Option`] could simply be created like this: /// ``` /// # #[macro_use] extern crate higher_free_macro; /// # use higher_free_macro::higher::*; /// free!(FreeOption<A>, Option<FreeOption<A>>); /// ``` -/// +/// /// The type created by this is indeed a Monad, as long as the wrapped type is [`Clone`]: /// ``` /// # #[macro_use] extern crate higher_free_macro; /// # use higher_free_macro::higher::*; /// free!(FreeOption<A>, Option<FreeOption<A>>); -/// +/// /// fn returns_a_monad<'a, A>(a : A) -> impl Monad<'a,A> where A : Clone + 'a { /// FreeOption::Pure(a) /// } @@ -111,25 +111,25 @@ pub extern crate higher; /// FreeOption::Pure(a) /// } /// ``` -/// +/// /// That said, the macro also supports multiple generic parameters. The parameter for which the traits will be implemented is the first generic parameter /// of the to-be-created Free Monad type. For instance, a Free Monad based on [`Result`] would be: /// ``` /// # #[macro_use] extern crate higher_free_macro; /// # use higher_free_macro::higher::*; /// free!(FreeResult<A,E>, Result<FreeResult<A,E>,E>); -/// -/// fn returns_a_monad<'a, A, E>(r : Result<A,E>) -> impl Monad<'a,A> +/// +/// fn returns_a_monad<'a, A, E>(r : Result<A,E>) -> impl Monad<'a,A> /// where A : Clone + 'a, E : Clone /// { /// FreeResult::lift_f(r) /// } /// ``` -/// +/// /// Furthermore, the use case that the lifetime of the Free Monad depends on the lifetime of the mapping functions is supported too. -/// This is particularly useful, because it enables the usage of (non-constant) continuation functions, what is a requirement for +/// This is particularly useful, because it enables the usage of (non-constant) continuation functions, what is a requirement for /// using the Free Monad for an embedded Domain Specific Language (eDSL). -/// +/// /// Such a [`Functor`][higher::Functor] could for instance look like this: /// ``` /// # #[macro_use] extern crate higher_free_macro; @@ -143,7 +143,7 @@ pub extern crate higher; /// } /// } /// ``` -/// +/// /// Sadly, the macro syntax is a bit more convoluted in this case. The relevant lifetime has to be stated explicitly as the first parameter, like this: /// ``` /// # #[macro_use] extern crate higher_free_macro; @@ -158,7 +158,7 @@ pub extern crate higher; /// # } /// # } /// ``` -/// +/// /// # Generated Functions /// In addition to the trait implementations for [`Bind`][higher::Bind], [`Functor`][higher::Functor], [`Apply`][higher::Apply] and [`Pure`][higher::Pure], /// the macro also generates associated functions for the Free Monad type. These functions are: @@ -168,11 +168,11 @@ pub extern crate higher; /// A concrete example will make this more clear. Let's take our `FreeOption<A>` example from above. In this case, the signatures are /// `fn lift_f(functor : Option<A>) -> FreeOption<A>` and /// `fn retract(self : FreeOption<A>) -> Option<A>` -/// +/// /// `lift_f()` converts a base Functor into the corresponding Free Monad, meaning that the Functor gets wrapped in `Free`, and the value it holds gets /// mapped into a `Pure`. The (simplified for readability) formula is: /// `Self::Free(functor.fmap(|a| Self::Pure(a)))` -/// +/// /// `retract()` is the left-inverse of `lift_f()`. `|x| retract(lift_f(x))` is (ignoring type coercion) equivalent to [`identity`][std::convert::identity]: /// ``` /// # #[macro_use] extern crate higher_free_macro; @@ -222,7 +222,7 @@ macro_rules! free { } } } - + impl<'free_macro_reserved_lifetime, $($other_lifetimes,)* $generic $(,$other_generics)*> $crate::higher::Functor<'free_macro_reserved_lifetime,$generic> for $name<$($other_lifetimes,)* $generic $(,$other_generics)*> { type Target<FreeMacroReservedType> = $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*>; fn fmap<FreeMacroReservedType,FreeMacroReservedType2>(self, f: FreeMacroReservedType2) -> Self::Target<FreeMacroReservedType> where FreeMacroReservedType2: Fn($generic) -> FreeMacroReservedType + 'free_macro_reserved_lifetime{ @@ -294,10 +294,10 @@ macro_rules! free { } } } - + impl<$($other_lifetimes : $a,)* $generic $(,$other_generics)*> $crate::higher::Functor<$a,$generic> for $name<$($other_lifetimes,)* $generic $(,$other_generics)*> where $generic : $a $(,$other_generics : $a)* { type Target<FreeMacroReservedType> = $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*>; - fn fmap<FreeMacroReservedType,F>(self, f: F) -> Self::Target<FreeMacroReservedType> + fn fmap<FreeMacroReservedType,F>(self, f: F) -> Self::Target<FreeMacroReservedType> where F: Fn($generic) -> FreeMacroReservedType + $a { fn __fmap_impl<$($other_lifetimes : $a,)* $generic $(,$other_generics)*, FreeMacroReservedType, F>(s : $name<$($other_lifetimes,)* $generic $(,$other_generics)*>, f : std::rc::Rc<F>) -> $name<$($other_lifetimes,)* FreeMacroReservedType $(,$other_generics)*> where $generic : $a $(,$other_generics : $a)*, F: Fn($generic) -> FreeMacroReservedType + $a{ @@ -352,139 +352,142 @@ macro_rules! free { } #[cfg(test)] -mod free_monad_tests{ - use higher::{Pure, Functor, Bind, Apply, apply::ApplyFn}; +mod free_monad_tests { + use higher::{apply::ApplyFn, Apply, Bind, Functor, Pure}; use super::free; - + free!(FreeVec<A>, Vec<FreeVec<A>>); #[test] - fn test_lift_f_no_lifetime(){ - let free = FreeVec::lift_f(vec![1,2,3]); + fn test_lift_f_no_lifetime() { + 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!() + 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!(), }, - FreeVec::Pure(_) => unreachable!() + FreeVec::Pure(_) => unreachable!(), } } #[test] - fn test_retract_no_lifetime(){ - let f = FreeVec::lift_f(vec![1,2,3]); + fn test_retract_no_lifetime() { + let f = FreeVec::lift_f(vec![1, 2, 3]); let v = f.retract(); - assert_eq!(v, vec![1,2,3]); + assert_eq!(v, vec![1, 2, 3]); } #[test] - fn test_pure_no_lifetime(){ + fn test_pure_no_lifetime() { let f = FreeVec::pure(3); match f { - FreeVec::Pure(v) => assert_eq!(v,3), + FreeVec::Pure(v) => assert_eq!(v, 3), FreeVec::Free(_) => unreachable!(), } } #[test] - fn test_fmap_no_lifetime(){ - let f = FreeVec::lift_f(vec![1,2,3]); - let f = f.fmap(|x| (f64::from(x))/2.0); + fn test_fmap_no_lifetime() { + let f = FreeVec::lift_f(vec![1, 2, 3]); + 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.5f64, 1f64, 1.5f64], vec![*a,*b,*c]); - }, - _ => unreachable!() + FreeVec::Free(f) => match &**f { + [FreeVec::Pure(a), FreeVec::Pure(b), FreeVec::Pure(c)] => { + assert_eq!(vec![0.5f64, 1f64, 1.5f64], vec![*a, *b, *c]); } + _ => unreachable!(), }, - FreeVec::Pure(_) => 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) + 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![f64::from(x),f64::from(x) + 1.0f64])} else { FreeVec::Pure(f64::from(x))}); + 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![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_nearly_equal!(*a, 1.0f64, f64::EPSILON); - match &***b { - [FreeVec::Pure(a), FreeVec::Pure(b)] => { - assert_nearly_equal!(*a, 2.0f64, f64::EPSILON); - assert_nearly_equal!(*b, 3.0f64, f64::EPSILON); - }, - _ => unreachable!() + FreeVec::Free(f) => match &**f { + [FreeVec::Pure(a), FreeVec::Free(b)] => { + assert_nearly_equal!(*a, 1.0f64, f64::EPSILON); + match &***b { + [FreeVec::Pure(a), FreeVec::Pure(b)] => { + assert_nearly_equal!(*a, 2.0f64, f64::EPSILON); + assert_nearly_equal!(*b, 3.0f64, f64::EPSILON); } - }, - _ => unreachable!() + _ => unreachable!(), + } } + _ => unreachable!(), }, - FreeVec::Pure(_) => unreachable!() + FreeVec::Pure(_) => unreachable!(), } } #[test] - fn test_apply_no_lifetime(){ - 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)]))])); + fn test_apply_no_lifetime() { + 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 = |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!() - } - }, - _ => unreachable!() + 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!(), } - }, - FreeVec::Pure(_) => unreachable!() - } + } + _ => unreachable!(), + }, + FreeVec::Pure(_) => unreachable!(), }; //now, where are those sub-trees exactly, in this monstrosity? match free_monad { - FreeVec::Free(vector) => { - match &**vector{ - [FreeVec::Free(left), right] => { - match &***left { - [a,b] => { - check_mlike_structure(a, 10i64,12i64,14i64); - check_mlike_structure(b, 7i64,8i64,9i64); - }, - _ => unreachable!() + FreeVec::Free(vector) => match &**vector { + [FreeVec::Free(left), right] => { + match &***left { + [a, b] => { + check_mlike_structure(a, 10i64, 12i64, 14i64); + check_mlike_structure(b, 7i64, 8i64, 9i64); } - check_mlike_structure(right, 0i64,1i64,2i64); - }, - _ => unreachable!() + _ => unreachable!(), + } + check_mlike_structure(right, 0i64, 1i64, 2i64); } + _ => unreachable!(), }, - FreeVec::Pure(_) => unreachable!() + FreeVec::Pure(_) => unreachable!(), } } @@ -493,42 +496,57 @@ mod free_monad_tests{ use std::rc::Rc; #[derive(Clone)] - struct Conti<'a,A,B>(Rc<dyn Fn(B)->A + 'a>, Rc<dyn Fn(B)->A + 'a>); //two fields, to make apply testable. - impl<'a,A : 'a,B : 'a> Functor<'a,A> for Conti<'a,A,B>{ - type Target<T> = Conti<'a,T,B>; - - fn fmap<C, F>(self, f: F) -> Self::Target<C> where F: Fn(A) -> C + 'a { + struct Conti<'a, A, B>(Rc<dyn Fn(B) -> A + 'a>, Rc<dyn Fn(B) -> A + 'a>); //two fields, to make apply testable. + impl<'a, A: 'a, B: 'a> Functor<'a, A> for Conti<'a, A, B> { + type Target<T> = Conti<'a, T, B>; + + fn fmap<C, F>(self, f: F) -> Self::Target<C> + where + F: Fn(A) -> C + 'a, + { let f = Rc::new(f); let g = f.clone(); - Conti(Rc::new(move |x| f((self.0)(x))), Rc::new(move |x| g((self.1)(x)))) + Conti( + Rc::new(move |x| f((self.0)(x))), + Rc::new(move |x| g((self.1)(x))), + ) } } //need Bind and Pure to test retract. This is dumb, but it should fulfill the monad laws: - impl<'a, A : 'a, B : Clone+'a> Bind<'a, A> for Conti<'a,A,B>{ - type Target<T> = Conti<'a,T,B>; + 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 { + fn bind<C, F>(self, f: F) -> Self::Target<C> + where + F: Fn(A) -> Self::Target<C> + 'a, + { let f = Rc::new(f); let g = f.clone(); let l = move |x| f((self.0)(x)); let r = move |x| g((self.1)(x)); - Conti(Rc::new(move |x| (l(x.clone())).0(x)), Rc::new(move |x| (r(x.clone())).1(x))) + Conti( + Rc::new(move |x| (l(x.clone())).0(x)), + Rc::new(move |x| (r(x.clone())).1(x)), + ) } } - impl<'a, A : Clone+'a, B : 'a> Pure<A> for Conti<'a,A,B>{ + 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())) + Conti( + Rc::new(move |_| value.clone()), + Rc::new(move |_| v2.clone()), + ) } } - + //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 binder = |x : u32| { + fn test_conti_monad_laws() { + let binder = |x: u32| { let y = x; - Conti::<u32,u32>(Rc::new(move |a| x + a*2), Rc::new(move |a| y * a+3)) + Conti::<u32, u32>(Rc::new(move |a| x + a * 2), Rc::new(move |a| y * a + 3)) }; let test1 = Conti::pure(7u32); let v1 = test1.bind(binder); @@ -536,18 +554,15 @@ mod free_monad_tests{ assert_eq!((v1.0)(13), (v2.0)(13)); assert_eq!((v1.1)(17), (v2.1)(17)); - let test2 = Conti(Rc::new(|a| 31 + a*5), Rc::new(|b| 32*b+3)); - let bound_with_pure =test2.clone().bind(Conti::pure); + 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 test3 = Conti(Rc::new(|a| 32 + (a*2)), Rc::new(|b| 32*b+7)); - let g = |x : u32| { - Conti::<u32,u32>(Rc::new(move |a| x*2 + a), Rc::new(move |a| x * a+7)) - }; - let h = |x : u32| { - Conti::<u32,u32>(Rc::new(move |a| x + a), Rc::new(move |a| x * a+12)) - }; + let test3 = Conti(Rc::new(|a| 32 + (a * 2)), Rc::new(|b| 32 * b + 7)); + let g = + |x: u32| Conti::<u32, u32>(Rc::new(move |a| x * 2 + a), Rc::new(move |a| x * a + 7)); + let h = |x: u32| Conti::<u32, u32>(Rc::new(move |a| x + a), Rc::new(move |a| x * a + 12)); let v1 = (test3.clone().bind(g)).bind(h); let v2 = test3.bind(|a| (g(a).bind(h))); @@ -557,64 +572,72 @@ mod free_monad_tests{ //well, looks monadic enough to me. Let's use it for the unit test of retract below. } - free!(<'a>, FreeConti<'a,A,B>, Conti<'a,FreeConti<'a,A,B>,B>); #[test] - fn test_lift_f_lifetime(){ - let f = FreeConti::lift_f(Conti(Rc::new((|x| x*2) as fn(u32) -> u32), Rc::new((|x| x+5) as fn(u32) -> u32))); + fn test_lift_f_lifetime() { + let f = FreeConti::lift_f(Conti( + Rc::new((|x| x * 2) as fn(u32) -> u32), + Rc::new((|x| x + 5) as fn(u32) -> u32), + )); match f { FreeConti::Free(m) => { - match (m.0)(4){ + match (m.0)(4) { FreeConti::Pure(v) => assert_eq!(v, 8), - FreeConti::Free(_) => unreachable!() + FreeConti::Free(_) => unreachable!(), } - match (m.1)(4){ + match (m.1)(4) { FreeConti::Pure(v) => assert_eq!(v, 9), - FreeConti::Free(_) => unreachable!() + FreeConti::Free(_) => unreachable!(), } - }, - FreeConti::Pure(_) => unreachable!() + } + FreeConti::Pure(_) => unreachable!(), } } #[test] - fn test_retract_lifetime(){ - let f = FreeConti::lift_f(Conti(Rc::new((|x| x*2) as fn(u32) -> u32), Rc::new((|x| x+5) as fn(u32) -> u32))); + fn test_retract_lifetime() { + let f = FreeConti::lift_f(Conti( + Rc::new((|x| x * 2) as fn(u32) -> u32), + Rc::new((|x| x + 5) as fn(u32) -> u32), + )); let r = f.retract(); assert_eq!((r.0)(4), 8); assert_eq!((r.1)(4), 9); } #[test] - fn test_fmap_lifetime(){ - let functor = Conti(Rc::new(|x : u32| i32::try_from(x).unwrap()*3+2), Rc::new(|x| (i32::try_from(x).unwrap()+2)*5)); + fn test_fmap_lifetime() { + 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); + let free_monad = free_monad.fmap(|x: i32| f64::from(x) * 0.25f64); match free_monad { FreeConti::Free(f) => { let left = (f.0)(7); match left { FreeConti::Pure(v) => { assert_nearly_equal!(v, 5.75f64, f64::EPSILON); - }, - FreeConti::Free(_) => unreachable!() + } + FreeConti::Free(_) => unreachable!(), } let right = (f.1)(7); match right { FreeConti::Pure(v) => { assert_nearly_equal!(v, 11.25f64, f64::EPSILON); - }, - FreeConti::Free(_) => unreachable!() + } + FreeConti::Free(_) => unreachable!(), } - }, - FreeConti::Pure(_) => unreachable!() + } + FreeConti::Pure(_) => unreachable!(), } } #[test] - fn test_pure_lifetime(){ - let f : FreeConti<_,()> = FreeConti::pure(27); + fn test_pure_lifetime() { + let f: FreeConti<_, ()> = FreeConti::pure(27); match f { FreeConti::Pure(v) => assert_eq!(v, 27), FreeConti::Free(_) => unreachable!(), @@ -622,12 +645,18 @@ mod free_monad_tests{ } #[test] - fn test_bind_lifetime(){ - let functor = Conti(Rc::new(|x : u32| i32::try_from(x).unwrap()*3+2), Rc::new(|x| (i32::try_from(x).unwrap()+2)*5)); + fn test_bind_lifetime() { + 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)))) + 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 free_monad { FreeConti::Free(f) => { @@ -641,11 +670,11 @@ mod free_monad_tests{ FreeConti::Free(_) => unreachable!(), } let right = (f.1)(5); - match right{ + match right { FreeConti::Pure(v) => assert_nearly_equal!(v, -11.5f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - }, + } FreeConti::Pure(_) => unreachable!(), } let right = (f.1)(4); @@ -662,26 +691,30 @@ mod free_monad_tests{ FreeConti::Pure(v) => assert_nearly_equal!(v, -27.5f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - }, + } FreeConti::Pure(_) => unreachable!(), } - }, - FreeConti::Pure(_) => unreachable!() + } + FreeConti::Pure(_) => unreachable!(), } } #[test] - fn test_apply_lifetime(){ + fn test_apply_lifetime() { //oh, god, please no. - 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| f64::from(y*i32::try_from(x).unwrap())).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| f64::from(y * i32::try_from(x).unwrap())).into()), + )); //make it stop! let free_monad = free_monad_input.apply(functions); - + match free_monad { FreeConti::Free(m) => { let left = (m.0)(5u32); @@ -689,15 +722,15 @@ mod free_monad_tests{ FreeConti::Free(m) => { let left = (m.0)(7u32); match left { - FreeConti::Pure(v) => assert_nearly_equal!(v,28f64,f64::EPSILON), + FreeConti::Pure(v) => assert_nearly_equal!(v, 28f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } let right = (m.1)(7u32); match right { - FreeConti::Pure(v) => assert_nearly_equal!(v,50f64, f64::EPSILON), + FreeConti::Pure(v) => assert_nearly_equal!(v, 50f64, f64::EPSILON), FreeConti::Free(_) => unreachable!(), } - }, + } FreeConti::Pure(_) => unreachable!(), } let right = (m.1)(5u32); @@ -705,21 +738,24 @@ mod free_monad_tests{ FreeConti::Free(m) => { let left = (m.0)(7u32); match left { - FreeConti::Pure(v) => assert_nearly_equal!(v,5f64*23f64, f64::EPSILON), + FreeConti::Pure(v) => { + assert_nearly_equal!(v, 5f64 * 23f64, f64::EPSILON) + } FreeConti::Free(_) => unreachable!(), } let right = (m.1)(7u32); match right { - FreeConti::Pure(v) => assert_nearly_equal!(v, 5f64*45f64, f64::EPSILON), + FreeConti::Pure(v) => { + assert_nearly_equal!(v, 5f64 * 45f64, f64::EPSILON) + } FreeConti::Free(_) => unreachable!(), } - }, + } FreeConti::Pure(_) => unreachable!(), } - }, - FreeConti::Pure(_) => unreachable!() + } + FreeConti::Pure(_) => unreachable!(), } //let's never speak of this again. } - -}
\ No newline at end of file +} diff --git a/tests/multiple_generics.rs b/tests/multiple_generics.rs index c8c76ba..6e744f4 100644 --- a/tests/multiple_generics.rs +++ b/tests/multiple_generics.rs @@ -3,79 +3,67 @@ //! Tests if multiple generic parameters work, for the case that lifetimes are independent of mapping functions. //! For simplicity, it just creates a `FreeResult` based on `Result`. +use higher::{Apply, Bind, Functor}; 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) + assert!((($a) - ($b)).abs() < $c) }; } #[test] -fn test_multiple_generics(){ - let m : FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); - let m = m.fmap(|x| x*2); +fn test_multiple_generics() { + let m: FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); + let m = m.fmap(|x| x * 2); let m = m.bind(|x| FreeResult::Free(Box::new(Ok(FreeResult::Pure(x))))); - let f = FreeResult::Pure((|x| x*3).into()); + let f = FreeResult::Pure((|x| x * 3).into()); let m = m.apply(f); match m { - FreeResult::Free(b) => { - match *b { - Ok(FreeResult::Free(b)) => { - match *b { - Ok(FreeResult::Pure(x)) => assert_eq!(x, 37*6), - _ => unreachable!() - } - } - _ => unreachable!() - } - } - FreeResult::Pure(_) => unreachable!() + FreeResult::Free(b) => match *b { + Ok(FreeResult::Free(b)) => match *b { + Ok(FreeResult::Pure(x)) => assert_eq!(x, 37 * 6), + _ => unreachable!(), + }, + _ => unreachable!(), + }, + FreeResult::Pure(_) => unreachable!(), } } #[test] -fn test_multiple_generics2(){ - let m : FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); +fn test_multiple_generics2() { + let m: FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); let m = m.bind(|_| FreeResult::<u32, _>::lift_f(Err("An early out.".to_owned()))); - match m{ - FreeResult::Free(m) => { - match &*m { - Ok(FreeResult::Free(m)) => { - match &**m { - Err(e) => assert_eq!(e, "An early out."), - _ => unreachable!() - } - }, - _ => unreachable!() - } + match m { + FreeResult::Free(m) => match &*m { + Ok(FreeResult::Free(m)) => match &**m { + Err(e) => assert_eq!(e, "An early out."), + _ => unreachable!(), + }, + _ => unreachable!(), }, - FreeResult::Pure(_) => 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| -> f64 {f64::from(x)*0.5f64}).fmap(Into::into); +fn test_multiple_generics3() { + let m: FreeResult<_, String> = FreeResult::lift_f(Ok(37u32)); + 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_nearly_equal!(18.5f64, *k, f64::EPSILON), - FreeResult::Free(_) => unreachable!(), - } - } - Err(_) => unreachable!(), - } + match m { + FreeResult::Free(m) => match &*m { + Ok(k) => match k { + FreeResult::Pure(k) => assert_nearly_equal!(18.5f64, *k, f64::EPSILON), + FreeResult::Free(_) => unreachable!(), + }, + Err(_) => unreachable!(), }, - FreeResult::Pure(_) => 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 48a9bd1..8d24f0e 100644 --- a/tests/multiple_generics_lifetime.rs +++ b/tests/multiple_generics_lifetime.rs @@ -2,32 +2,39 @@ #![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; +use higher::{Apply, Bind, Functor}; use higher_free_macro::free; -use higher::{Functor, Bind, Apply}; +use std::rc::Rc; #[derive(Clone)] -struct TestFunctor<'a, 'b, A, B>{ - data : &'b B, - next : Rc<dyn Fn(i32)->A + 'a>, +struct TestFunctor<'a, 'b, A, B> { + data: &'b B, + next: Rc<dyn Fn(i32) -> A + 'a>, } -impl<'a,'b,A : 'a,B> Functor<'a,A> for TestFunctor<'a, 'b, A, B>{ +impl<'a, 'b, A: 'a, B> Functor<'a, A> for TestFunctor<'a, 'b, A, B> { type Target<T> = TestFunctor<'a, 'b, T, B>; fn fmap<C, F>(self, f: F) -> Self::Target<C> where - F: Fn(A) -> C + 'a { - TestFunctor{ data : self.data, next : Rc::new(move |x| f((self.next)(x)))} + F: Fn(A) -> C + 'a, + { + TestFunctor { + data: self.data, + next: Rc::new(move |x| f((self.next)(x))), + } } } free!(<'xx>, FreeTest<'xx,'yy,AA,BB>, TestFunctor<'xx, 'yy, FreeTest<'xx, 'yy, AA, BB>, BB>); #[test] -fn test_lifetime_multiple_generics(){ - 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); +fn test_lifetime_multiple_generics() { + 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) => { @@ -35,44 +42,51 @@ fn test_lifetime_multiple_generics(){ let x = m.next.clone(); let y = m.next.clone(); let m1 = x(1); - match m1{ + match m1 { FreeTest::Pure(v) => assert!(!v), FreeTest::Free(_) => unreachable!(), } let m2 = y(3); - match m2{ + match m2 { FreeTest::Pure(v) => assert!(v), FreeTest::Free(_) => unreachable!(), } - }, - FreeTest::Pure(_) => 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) + 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| f64::from(x)*0.5f64)}); - let m = m.bind(|x : f64| -> FreeTest<_,_> { +fn test_lifetime_multiple_generics_bind() { + 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()) } else { - FreeTest::lift_f(TestFunctor{data : &"Now it's Little Big.", next : Rc::new(move |y| f64::from(y) + x.ceil())}) - }}); - match m{ + 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){ + match (m.next)(-3) { FreeTest::Pure(v) => assert_nearly_equal!(v, 1f64, f64::EPSILON), FreeTest::Free(_) => unreachable!(), } - match (m.next)(3){ + match (m.next)(3) { FreeTest::Pure(_) => unreachable!(), FreeTest::Free(v) => { assert_eq!(v.data, &"Now it's Little Big."); @@ -80,9 +94,9 @@ fn test_lifetime_multiple_generics_bind(){ FreeTest::Pure(v) => assert_nearly_equal!(v, 7f64, f64::EPSILON), FreeTest::Free(_) => unreachable!(), } - }, + } } - }, - FreeTest::Pure(_) => unreachable!() + } + FreeTest::Pure(_) => unreachable!(), } -}
\ No newline at end of file +} diff --git a/tests/trivial.rs b/tests/trivial.rs index 5bc1e91..d3a71f3 100644 --- a/tests/trivial.rs +++ b/tests/trivial.rs @@ -1,8 +1,8 @@ #![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::{Apply, Bind, Functor}; use higher_free_macro::free; -use higher::{Functor, Bind, Apply}; #[derive(Functor, Clone)] struct TrivialFunctor<A>(A); @@ -12,30 +12,22 @@ free!(TrivialFreeMonad<A>, TrivialFunctor<TrivialFreeMonad<A>>); #[test] fn test_trivial_functor() { let m = TrivialFreeMonad::lift_f(TrivialFunctor(37u32)); - let m = m.fmap(|x| x*2); + let m = m.fmap(|x| x * 2); let m = m.bind(|x| TrivialFreeMonad::Free(Box::new(TrivialFunctor(TrivialFreeMonad::Pure(x))))); - let f = TrivialFreeMonad::Pure((|x| x*3).into()); + let f = TrivialFreeMonad::Pure((|x| x * 3).into()); let m = m.apply(f); match m { - TrivialFreeMonad::Free(b) => { - match *b { - TrivialFunctor(f) => { - match f { - TrivialFreeMonad::Free(b) => { - match *b { - TrivialFunctor(f) => { - match f{ - TrivialFreeMonad::Pure(x) => assert_eq!(x, 37*6), - TrivialFreeMonad::Free(_) => unreachable!() - } - } - } - }, - TrivialFreeMonad::Pure(_) => unreachable!() - } - } - } - } - TrivialFreeMonad::Pure(_) => unreachable!() + TrivialFreeMonad::Free(b) => match *b { + TrivialFunctor(f) => match f { + TrivialFreeMonad::Free(b) => match *b { + TrivialFunctor(f) => match f { + TrivialFreeMonad::Pure(x) => assert_eq!(x, 37 * 6), + TrivialFreeMonad::Free(_) => unreachable!(), + }, + }, + TrivialFreeMonad::Pure(_) => unreachable!(), + }, + }, + TrivialFreeMonad::Pure(_) => unreachable!(), } -}
\ No newline at end of file +} diff --git a/tests/trivial_lifetime.rs b/tests/trivial_lifetime.rs index 4f7d094..04d2d4a 100644 --- a/tests/trivial_lifetime.rs +++ b/tests/trivial_lifetime.rs @@ -1,39 +1,44 @@ #![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}; +use higher::{Bind, Functor}; use higher_free_macro::free; +use std::rc::Rc; #[derive(Clone)] -struct TrivWithLifetime<'a,A,B>{ - next : Rc<dyn Fn(B)->A + 'a>, +struct TrivWithLifetime<'a, A, B> { + next: Rc<dyn Fn(B) -> A + 'a>, } -impl<'a,A : 'a,B : 'a> Functor<'a,A> for TrivWithLifetime<'a,A,B> { - type Target<T> = TrivWithLifetime<'a,T,B>; +impl<'a, A: 'a, B: 'a> Functor<'a, A> for TrivWithLifetime<'a, A, B> { + type Target<T> = TrivWithLifetime<'a, T, B>; fn fmap<C, F>(self, f: F) -> Self::Target<C> where - F: Fn(A) -> C + 'a { - TrivWithLifetime{ next : Rc::new(move |x| f((self.next)(x)))} + F: Fn(A) -> C + 'a, + { + TrivWithLifetime { + next: Rc::new(move |x| f((self.next)(x))), + } } } 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(i32::unsigned_abs)}); +fn test_trivial_with_lifetime() { + 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); match n { - FreeTriv::Pure(v) => assert_eq!(v,4u32), + FreeTriv::Pure(v) => assert_eq!(v, 4u32), FreeTriv::Free(_) => unreachable!(), } - }, - FreeTriv::Pure(_) => unreachable!() + } + FreeTriv::Pure(_) => unreachable!(), } -}
\ No newline at end of file +} diff --git a/tests/vector.rs b/tests/vector.rs index 2b2f4b2..f22a243 100644 --- a/tests/vector.rs +++ b/tests/vector.rs @@ -3,74 +3,80 @@ //! 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. +use higher::{Apply, Bind, Functor}; use higher_free_macro::free; -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) + assert!((($a) - ($b)).abs() < $c) }; } #[test] -fn test_vector(){ - 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]); +fn test_vector() { + 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{ - [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!() + FreeVec::Free(v) => match &**v { + [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); } - 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!() + _ => 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!() + _ => 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!() + _ => 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); } - 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!() + _ => 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!() + _ => unreachable!(), + } } - }, - _ => unreachable!() + _ => unreachable!(), + } } - } - FreeVec::Pure(_) => unreachable!() + _ => unreachable!(), + }, + FreeVec::Pure(_) => unreachable!(), } -}
\ No newline at end of file +} diff --git a/tests/with_lifetimes.rs b/tests/with_lifetimes.rs index 697fc74..ee35274 100644 --- a/tests/with_lifetimes.rs +++ b/tests/with_lifetimes.rs @@ -3,34 +3,47 @@ //! 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. +use higher::{Apply, Bind, Functor}; use higher_free_macro::free; -use higher::{Functor, Bind, Apply}; #[derive(Functor, Clone)] -struct WithLifetimes<'a,'b, A>{ - s1 : &'a str, - s2 : &'b str, - next : A +struct WithLifetimes<'a, 'b, A> { + s1: &'a str, + s2: &'b str, + next: A, } -free!(FreeWithLifetimes<'a,'b,A>, WithLifetimes<'a,'b,FreeWithLifetimes<'a,'b,A>>); +free!( + FreeWithLifetimes<'a, 'b, A>, + WithLifetimes<'a, 'b, FreeWithLifetimes<'a, 'b, A>> +); -fn lifetime_helper<'a,'b>(s1 : &'a str, s2 : &'b str) -> FreeWithLifetimes<'a, 'b, u32>{ - let fv = FreeWithLifetimes::lift_f(WithLifetimes{ s1, s2, next: 15}); - fv.fmap(|x| x+1) +fn lifetime_helper<'a, 'b>(s1: &'a str, s2: &'b str) -> FreeWithLifetimes<'a, 'b, u32> { + let fv = FreeWithLifetimes::lift_f(WithLifetimes { s1, s2, next: 15 }); + fv.fmap(|x| x + 1) } #[test] -fn test_with_lifetimes(){ +fn test_with_lifetimes() { let s1 = "First"; let s2 = "Second"; let fv = lifetime_helper(s1, s2); let s3 = "Third"; let s4 = "Fourth"; - let fv = fv.bind(|x| FreeWithLifetimes::lift_f(WithLifetimes{ s1: s3, s2: s4, next : x+2})); + let fv = fv.bind(|x| { + FreeWithLifetimes::lift_f(WithLifetimes { + s1: s3, + s2: s4, + next: x + 2, + }) + }); let s5 = "Fifth"; let s6 = "Sixth"; - let fa = FreeWithLifetimes::lift_f(WithLifetimes{s1: s5, s2: s6, next : (|x| x+3).into()}); + let fa = FreeWithLifetimes::lift_f(WithLifetimes { + s1: s5, + s2: s6, + next: (|x| x + 3).into(), + }); let fv = fv.apply(fa); match fv { FreeWithLifetimes::Free(v) => { @@ -48,15 +61,15 @@ fn test_with_lifetimes(){ FreeWithLifetimes::Free(_) => unreachable!(), FreeWithLifetimes::Pure(a) => { assert_eq!(a, 21); - }, + } } - }, + } FreeWithLifetimes::Pure(_) => unreachable!(), } - }, + } FreeWithLifetimes::Pure(_) => unreachable!(), } - }, - FreeWithLifetimes::Pure(_) => unreachable!() + } + FreeWithLifetimes::Pure(_) => unreachable!(), } -}
\ No newline at end of file +} |
