diff --git a/README.org b/README.org index 4f4ded5..aaf8be3 100644 --- a/README.org +++ b/README.org @@ -1,5 +1,5 @@ * effers -effect handlers in rust +ergonomic effect handlers in rust ** how to use *** defining effects @@ -7,24 +7,30 @@ effects are defined with traits #+begin_src rust trait Printer { - fn print(&mut self, s: &str); + fn print(&self, s: &str); + fn available() -> bool; } trait Logger { fn debug(&mut self, s: &str); - fn info(&mut self, s: &str); + fn info(self, s: &str); } #+end_src -all the trait functions (that are used in programs) must take ~&mut self~ as a parameter +functions can take ~self~, ~&self~, ~&mut self~, or no ~self~ parameter. at this point ~self~ parameters with a specified type (like ~self: Box~) are not supported *** defining a program -programs are defined as a normal function, with the added ~program~ attribute +programs are defined as a normal function, with the added ~program~ attribute, which specifies (optional) a name for the program, and (required) the list of effects and corresponding functions that are used #+begin_src rust -#[program(Smth => Printer(print as p), Logger(debug, info))] -fn smth(val: u8) -> u8 { - p("hey hi hello"); +#[effers::program(MyCoolProgram => + Printer(print(&self) as p, available as printer_available), + Logger(debug(&mut self), info(self)) +)] +fn my_program(val: u8) -> u8 { + if printer_available() { + p("hey hi hello"); + } debug("this is a debug-level log"); info("this is a info-level log"); @@ -33,22 +39,47 @@ fn smth(val: u8) -> u8 { } #+end_src -the first token (~Smth~) will be the name of the program. effects are listed after the ~=>~ token +**** name + +the first token (~MyCoolProgram~) will be the name of the program. this is optional, and can be skipped: + +#+begin_src rust +#[program( + Printer(print(&self) as p, available as printer_available), + Logger(debug(&mut self), info(self)) +)] +#+end_src + +if skipped, the default name will be the program function's name (~my_program~) in PascalCase (~MyProgram~) **** listing effects effects are listed by writing the trait's name, followed by a parenthesized list of the functions that will be used -functions can be given an alias using the ~as~ keyword +***** listing effect functions + +due to limitations of proc-macros, it's unknown what kind of ~self~ parameter the function takes, if any, and so it has to be explicitly specified (if you have ideas on how to fix this, please open a PR!): here's how each type is specified: + +- ~fn print();~: ~print~ +- ~fn print(self);~: ~print(self)~ +- ~fn print(mut self);~: ~print(self)~ +- ~fn print(&self);~: ~print(&self)~ +- ~fn print(&mut self);~: ~print(&mut self)~ + +***** effect function aliases +functions can be given an alias using the ~as~ keyword (~print(&self) as p~) so that the function can be called by a different name inside the program *** defining effect handlers -effect handlers are defined by declaring a struct, and implementing the trait on it +effect handlers are defined by declaring a struct, and implementing the corresponding trait on it #+begin_src rust struct IoPrinter; impl Printer for IoPrinter { - fn print(&mut self, s: &str) { + fn print(&self, s: &str) { println!("{}", s) } + fn available() -> bool { + true + } } struct FileLogger; @@ -56,88 +87,21 @@ impl Logger for FileLogger { fn debug(&mut self, s: &str) { println!("debug: {}", s) } - fn info(&mut self, s: &str) { + fn info(self, s: &str) { println!("info: {}", s) } } #+end_src *** running programs -programs are run by providing the corresponding handlers in order, and finally calling the ~run~ method, providing it the required parameters +programs are run by providing the corresponding handlers *in the order listed in the program definition*, and finally calling the ~run~ method, providing it the required parameters #+begin_src rust -let result: u8 = Smth.add(IoPrinter).add(FileLogger).run(3); +let result: u8 = MyCoolProgram.add(IoPrinter).add(FileLogger).run(3); assert_eq!(result, 6); #+end_src -** full example -#+begin_src rust -use effers::program; - -#[program(Smth => Printer(print as p), Logger(debug, info))] -fn smth(val: u8) -> u8 { - p("hey hi hello"); - - debug("this is a debug-level log"); - info("this is a info-level log"); - - val + 3 -} - -fn main() { - // maybe smth like this? - let result: u8 = Smth.add(IoPrinter).add(FileLogger).run(3); - assert_eq!(result, 6); - let other_result: u8 = Smth - .add(IoPrinter) - .add(NetworkLogger { - credentials: "secret password".to_string(), - }) - .run(8); - assert_eq!(other_result, 11); -} - -// effects -trait Printer { - fn print(&mut self, s: &str); -} -trait Logger { - fn debug(&mut self, s: &str); - fn info(&mut self, s: &str); -} - -struct IoPrinter; -impl Printer for IoPrinter { - fn print(&mut self, s: &str) { - println!("{}", s) - } -} - -struct FileLogger; -impl Logger for FileLogger { - fn debug(&mut self, s: &str) { - println!("debug: {}", s) - } - fn info(&mut self, s: &str) { - println!("info: {}", s) - } -} - -struct NetworkLogger { - credentials: String, -} -impl Logger for NetworkLogger { - fn debug(&mut self, s: &str) { - println!( - "debug through network: {}; with password {}", - s, self.credentials - ) - } - fn info(&mut self, s: &str) { - println!( - "info through network: {}; with password {}", - s, self.credentials - ) - } -} -#+end_src +** examples +- [[./examples/main.rs][main: general use case]] +- [[./examples/clone.rs][clone: how cloning and copying programs works]] +- [[./examples/module.rs][module: effects from other modules are supported]] diff --git a/effers-derive/src/intermediate_structs.rs b/effers-derive/src/intermediate_structs.rs index 162658a..5b38383 100644 --- a/effers-derive/src/intermediate_structs.rs +++ b/effers-derive/src/intermediate_structs.rs @@ -66,6 +66,7 @@ pub fn intermediate_structs(args: &Args, prog_name: &Ident) -> Vec(#last, #last_letter); }, id, diff --git a/effers-derive/src/lib.rs b/effers-derive/src/lib.rs index cfe00b1..c017d65 100644 --- a/effers-derive/src/lib.rs +++ b/effers-derive/src/lib.rs @@ -57,6 +57,7 @@ fn rewrite_item_into_struct(func: ItemFn, args: Args) -> TokenStream { let final_impl = final_impl(intermediate_structs.last().unwrap(), func, &args); let out = quote! { + #[derive(Clone, Copy)] struct #prog_name; #inters_tokens diff --git a/examples/clone.rs b/examples/clone.rs new file mode 100644 index 0000000..903df18 --- /dev/null +++ b/examples/clone.rs @@ -0,0 +1,71 @@ +use effers::program; + +#[program(Incrementer(increment(&self)), Printer(print(&self)))] +fn prog(val: u8) -> u8 { + let x = increment(val); + let y = increment(x); + + print(x); + + x + y +} + +pub trait Incrementer { + fn increment(&self, v: u8) -> u8; +} + +#[derive(Clone, Copy)] +pub struct TestInc; +impl Incrementer for TestInc { + fn increment(&self, v: u8) -> u8 { + v + 3 + } +} + +trait Printer { + fn print(&self, s: u8); +} + +#[derive(Clone, Copy)] +struct Printer1; +impl Printer for Printer1 { + fn print(&self, s: u8) { + println!("1: {}", s) + } +} +#[derive(Clone)] +struct Printer2 { + prefix: String, +} +impl Printer for Printer2 { + fn print(&self, s: u8) { + println!("2: {} {}", self.prefix, s) + } +} + +fn main() { + // if a program only has Clone effects, the program also becomes clone + // same applies for Copy + + // a is Copy since TestInc is Copy + let a = Prog.add(TestInc); + + let b = a.add(Printer1); + let c = a.add(Printer2 { + prefix: "this is a number".to_string(), + }); + + // both TestInc and Printer1 are Copy, + // therefore b is copy, and we can call it as much as we want + let first_result = b.run(0); + assert_eq!(first_result, 9); + let second_result = b.run(2); + assert_eq!(second_result, 13); + + // since Printer2 is not Copy, but it is Clone, + // c is Clone but not Copy + let first_result = c.clone().run(0); + assert_eq!(first_result, 9); + let second_result = c.run(2); + assert_eq!(second_result, 13); +} diff --git a/examples/main.rs b/examples/main.rs index 16f0b2a..56db624 100644 --- a/examples/main.rs +++ b/examples/main.rs @@ -1,8 +1,11 @@ use effers::program; -#[program(Smth => Printer(print(&self) as p, check as check_printer), Logger(debug(&mut self), info(self)))] -fn smth(val: u8) -> u8 { - if check_printer() { +#[program(MyCoolProgram => + Printer(print(&self) as p, available as printer_available), + Logger(debug(&mut self), info(self)) +)] +fn my_program(val: u8) -> u8 { + if printer_available() { p("hey hi hello"); } @@ -19,9 +22,9 @@ fn other_program() { fn main() { // call the first program twice - let result: u8 = Smth.add(IoPrinter).add(FileLogger).run(3); + let result: u8 = MyCoolProgram.add(IoPrinter).add(FileLogger).run(3); assert_eq!(result, 6); - let other_result: u8 = Smth + let other_result: u8 = MyCoolProgram .add(IoPrinter) .add(NetworkLogger { credentials: "secret password".to_string(), @@ -35,7 +38,7 @@ fn main() { trait Printer { fn print(&self, s: &str); - fn check() -> bool; + fn available() -> bool; } trait Logger { fn debug(&mut self, s: &str); @@ -47,7 +50,7 @@ impl Printer for IoPrinter { fn print(&self, s: &str) { println!("{}", s) } - fn check() -> bool { + fn available() -> bool { true } } diff --git a/examples/path.rs b/examples/module.rs similarity index 72% rename from examples/path.rs rename to examples/module.rs index 313cb0e..a619a85 100644 --- a/examples/path.rs +++ b/examples/module.rs @@ -1,7 +1,5 @@ -use effers::program; - // Effects can be referenced from inside a module -#[program(inc::Incrementer(increment(&self)))] +#[effers::program(inc::Incrementer(increment(&self)))] fn prog(val: u8) -> u8 { let x = increment(val); let y = increment(x); @@ -11,15 +9,10 @@ fn prog(val: u8) -> u8 { mod inc { pub trait Incrementer { fn increment(&self, v: u8) -> u8; - - fn check() -> bool; } pub struct TestInc; impl Incrementer for TestInc { - fn check() -> bool { - false - } fn increment(&self, v: u8) -> u8 { v + 3 } diff --git a/src/lib.rs b/src/lib.rs index b6e8ef2..aa3d46d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ mod test { } } - #[program(Printer(print as p))] + #[program(Printer(print(&self) as p))] fn ohter() { let _s = p("hey hi hello"); }