readme and examples n stuff

main
annieversary 2022-01-21 13:16:03 +00:00
parent a67ad2d6ad
commit 64637fa92d
7 changed files with 135 additions and 102 deletions

View File

@ -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<Self>~) 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 {
#[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]]

View File

@ -66,6 +66,7 @@ pub fn intermediate_structs(args: &Args, prog_name: &Ident) -> Vec<IntermediateS
let last_letter = letters.clone().last();
structs.push(IntermediateStruct::new(
quote! {
#[derive(Clone, Copy)]
struct #id<#generics>(#last, #last_letter);
},
id,

View File

@ -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

71
examples/clone.rs Normal file
View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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");
}