Skip to content

Repository files navigation

effect-light

effect-lite

An experimental, lightweight (no-std compatible), opinionated algebraic effects library for Rust.

Goals

  • No-std compatibility (feature gated).
  • Use zero-cost abstractions.
  • Easy onboarding.

Non-goals

  • Incorporate advanced runtimes or application frameworks - this should be a low-level crate with higher level features built into dependent crates.
  • Use traditional FP method names at the expense of onboarding difficulty - this is an opinionated crate and I am not a purist.

Examples

Inversion of control

// An effect that requires access to the inaccessible application state in the parent struct.

use effect_light::Effect;#[derive(Default)]structAppState{count:usize,component: component::Component,}mod component {#[derive(Default)]pubstructComponent{pubmessage:String}implComponent{pubfnhandle_message(&mutself,msg:String) -> impl effect_light::Effect<&mutsuper::AppState> + 'static{self.message = msg;
effect_light::fn_effect(|app_state:&mutsuper::AppState| app_state.count +=1)}}}letmut state = AppState::default();let effect = state.component.handle_message("Hello, world!".to_string());assert_eq!(state.component.message,"Hello, world!");assert_eq!(state.count,0);
effect.resolve(&mut state);assert_eq!(state.count,1);

Dependency injection

// Define generic effects, and resolve them using any type that meets the trait bounds.

use effect_light::Effect;traitFoo{}structProdFoo;structTestFoo;implFooforProdFoo{}implFooforTestFoo{}structFooEffect;impl<F:Foo>Effect<&F>forFooEffect{typeOutput = ();fnresolve(self,dependency:&F){}}let(prod, test) = (ProdFoo,TestFoo);// FooEffect can be resolved against both the test and prod 'foo'.FooEffect.resolve(&prod);FooEffect.resolve(&test);

Centralise side effects

use effect_light::Effect;structStdoutPrinter;implStdoutPrinter{fnprint(&self,text:&str){println!("{text}");}}let printer = StdoutPrinter;let effect = effect_light::fn_effect(|printer:&StdoutPrinter| printer.print("Hello, world"));// Prints to console here.
effect.resolve(&StdoutPrinter);

Dependency injection - async

// An effect that requires access to a reqwest::Client.

use effect_light::Effect;let client = reqwest::Client::new();let effect = effect_light::fn_effect_async(async |client:&reqwest::Client|
client.get("https://www.google.com").send().await.unwrap().text().await.unwrap());let future = effect.resolve(&client);let rt = tokio::runtime::Runtime::new().unwrap();let output = rt.block_on(future);println!("{output}");

Module-level access control

// An effect that requires access to an inaccessible application state

use effect_light::{Effect,EffectExt};#[derive(Default)]structAppState{component: component::Component,}mod component {// Note no public setters, so parent modules can't change content...#[derive(Default)]pubstructComponent{content:String}implComponent{pubfnupdate_content<'a>() -> impl effect_light::Effect<(&'amutSelf,&'a reqwest::Client),Output=implFuture<Output = ()>>{// ...but we can provide parent module an Effect that grants the required access.
effect_light::fn_effect_async(|(this, client):(&mutSelf,&reqwest::Client)| asyncmove{let output = client.get("https://www.google.com").send().await.unwrap().text().await.unwrap();
this.content = output;})}pubfnget_content(&self) -> &str{&self.content}}}letmut state = AppState::default();let client = reqwest::Client::new();let effect = component::Component::update_content().map_dependency(|(this, client):(&mutAppState,_)| (&mut this.component, client));let future = effect.resolve((&mut state,&client));let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(future);// content should contain the body of Google.comprintln!("{}", state.component.get_content());

License: MIT

About

Lightweight, no-std compatible algebraic effects library for Rust

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages