extern crate alloc;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::future::Future;
use core::marker::PhantomData;
use core::pin::Pin;
use super::fibra::FibraId;
#[derive(Debug, Clone)]
pub enum Causa<E> {
Defectus(E),
Mors(String),
Interruptio(FibraId),
Utrumque(Box<Causa<E>>, Box<Causa<E>>),
Deinde(Box<Causa<E>>, Box<Causa<E>>),
Vacua,
}
impl<E> Causa<E> {
#[inline]
pub fn defectus(e: E) -> Self {
Causa::Defectus(e)
}
#[inline]
pub fn mors(msg: impl Into<String>) -> Self {
Causa::Mors(msg.into())
}
#[inline]
pub fn interruptio(fibra_id: FibraId) -> Self {
Causa::Interruptio(fibra_id)
}
#[inline]
pub fn both(self, other: Causa<E>) -> Self {
match (&self, &other) {
(Causa::Vacua, _) => other,
(_, Causa::Vacua) => self,
_ => Causa::Utrumque(Box::new(self), Box::new(other)),
}
}
#[inline]
pub fn then(self, other: Causa<E>) -> Self {
match (&self, &other) {
(Causa::Vacua, _) => other,
(_, Causa::Vacua) => self,
_ => Causa::Deinde(Box::new(self), Box::new(other)),
}
}
#[inline]
pub fn is_defectus(&self) -> bool {
matches!(self, Causa::Defectus(_))
}
#[inline]
pub fn is_mors(&self) -> bool {
matches!(self, Causa::Mors(_))
}
#[inline]
pub fn is_interruptio(&self) -> bool {
matches!(self, Causa::Interruptio(_))
}
#[inline]
pub fn defect(&self) -> Option<&E> {
match self {
Causa::Defectus(e) => Some(e),
_ => None,
}
}
pub fn defects(&self) -> Vec<&E> {
let mut result = Vec::new();
self.collect_defects(&mut result);
result
}
fn collect_defects<'a>(&'a self, result: &mut Vec<&'a E>) {
match self {
Causa::Defectus(e) => result.push(e),
Causa::Utrumque(a, b) | Causa::Deinde(a, b) => {
a.collect_defects(result);
b.collect_defects(result);
}
_ => {}
}
}
pub fn map_error<F, E2>(self, f: F) -> Causa<E2>
where
F: Fn(E) -> E2 + Clone,
{
match self {
Causa::Defectus(e) => Causa::Defectus(f(e)),
Causa::Mors(s) => Causa::Mors(s),
Causa::Interruptio(id) => Causa::Interruptio(id),
Causa::Utrumque(a, b) => {
Causa::Utrumque(Box::new(a.map_error(f.clone())), Box::new(b.map_error(f)))
}
Causa::Deinde(a, b) => {
Causa::Deinde(Box::new(a.map_error(f.clone())), Box::new(b.map_error(f)))
}
Causa::Vacua => Causa::Vacua,
}
}
}
impl<E: core::fmt::Display> core::fmt::Display for Causa<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Causa::Defectus(e) => write!(f, "Defectus: {e}"),
Causa::Mors(s) => write!(f, "Mors: {s}"),
Causa::Interruptio(id) => write!(f, "Interruptio: {id}"),
Causa::Utrumque(a, b) => write!(f, "({a} ∧ {b})"),
Causa::Deinde(a, b) => write!(f, "({a} → {b})"),
Causa::Vacua => write!(f, "Vacua"),
}
}
}
#[derive(Debug, Clone)]
pub enum Exitus<E, A> {
Successus(A),
Defectio(Causa<E>),
}
impl<E, A> Exitus<E, A> {
#[inline]
pub fn successus(a: A) -> Self {
Exitus::Successus(a)
}
#[inline]
pub fn defectio(e: E) -> Self {
Exitus::Defectio(Causa::defectus(e))
}
#[inline]
pub fn defectio_causa(causa: Causa<E>) -> Self {
Exitus::Defectio(causa)
}
#[inline]
pub fn mors(msg: impl Into<String>) -> Self {
Exitus::Defectio(Causa::mors(msg))
}
#[inline]
pub fn interruptio(fibra_id: FibraId) -> Self {
Exitus::Defectio(Causa::interruptio(fibra_id))
}
#[inline]
pub fn is_successus(&self) -> bool {
matches!(self, Exitus::Successus(_))
}
#[inline]
pub fn is_defectio(&self) -> bool {
matches!(self, Exitus::Defectio(_))
}
#[inline]
pub fn successus_value(&self) -> Option<&A> {
match self {
Exitus::Successus(a) => Some(a),
Exitus::Defectio(_) => None,
}
}
#[inline]
pub fn causa(&self) -> Option<&Causa<E>> {
match self {
Exitus::Defectio(c) => Some(c),
Exitus::Successus(_) => None,
}
}
#[inline]
pub fn map<B, F>(self, f: F) -> Exitus<E, B>
where
F: FnOnce(A) -> B,
{
match self {
Exitus::Successus(a) => Exitus::Successus(f(a)),
Exitus::Defectio(c) => Exitus::Defectio(c),
}
}
#[inline]
pub fn map_error<F, E2>(self, f: F) -> Exitus<E2, A>
where
F: Fn(E) -> E2 + Clone,
{
match self {
Exitus::Successus(a) => Exitus::Successus(a),
Exitus::Defectio(c) => Exitus::Defectio(c.map_error(f)),
}
}
#[inline]
pub fn to_result(self) -> Result<A, Causa<E>> {
match self {
Exitus::Successus(a) => Ok(a),
Exitus::Defectio(c) => Err(c),
}
}
#[inline]
pub fn from_result(result: Result<A, E>) -> Self {
match result {
Ok(a) => Exitus::successus(a),
Err(e) => Exitus::defectio(e),
}
}
#[inline]
pub fn flat_map<B, F>(self, f: F) -> Exitus<E, B>
where
F: FnOnce(A) -> Exitus<E, B>,
{
match self {
Exitus::Successus(a) => f(a),
Exitus::Defectio(c) => Exitus::Defectio(c),
}
}
#[inline]
pub fn fold<B, FS, FF>(self, on_success: FS, on_failure: FF) -> B
where
FS: FnOnce(A) -> B,
FF: FnOnce(Causa<E>) -> B,
{
match self {
Exitus::Successus(a) => on_success(a),
Exitus::Defectio(c) => on_failure(c),
}
}
}
impl<E, A> From<Result<A, E>> for Exitus<E, A> {
#[inline]
fn from(result: Result<A, E>) -> Self {
Exitus::from_result(result)
}
}
#[derive(Debug, Clone)]
pub struct Ambitus<R> {
value: R,
}
impl<R> Ambitus<R> {
#[inline]
pub fn new(value: R) -> Self {
Ambitus { value }
}
#[inline]
pub fn get(&self) -> &R {
&self.value
}
#[inline]
pub fn get_mut(&mut self) -> &mut R {
&mut self.value
}
#[inline]
pub fn into_inner(self) -> R {
self.value
}
#[inline]
pub fn map<S, F>(self, f: F) -> Ambitus<S>
where
F: FnOnce(R) -> S,
{
Ambitus::new(f(self.value))
}
}
impl<R: Default> Default for Ambitus<R> {
fn default() -> Self {
Ambitus::new(R::default())
}
}
pub type ZioRunner<R, E, A> =
Box<dyn FnOnce(Ambitus<R>) -> Pin<Box<dyn Future<Output = Exitus<E, A>> + Send>> + Send>;
pub struct Zio<R, E, A> {
run: ZioRunner<R, E, A>,
_phantom: PhantomData<(R, E, A)>,
}
impl<R: Send + 'static, E: Send + 'static, A: Send + 'static> Zio<R, E, A> {
#[inline]
pub fn succeed(a: A) -> Self
where
A: Send,
{
Zio {
run: Box::new(move |_| Box::pin(async move { Exitus::successus(a) })),
_phantom: PhantomData,
}
}
#[inline]
pub fn fail(e: E) -> Self
where
E: Send,
{
Zio {
run: Box::new(move |_| Box::pin(async move { Exitus::defectio(e) })),
_phantom: PhantomData,
}
}
#[inline]
pub fn die(msg: impl Into<String> + Send + 'static) -> Self {
let msg = msg.into();
Zio {
run: Box::new(move |_| Box::pin(async move { Exitus::mors(msg) })),
_phantom: PhantomData,
}
}
#[inline]
pub fn from_async<F, Fut>(f: F) -> Self
where
F: FnOnce(Ambitus<R>) -> Fut + Send + 'static,
Fut: Future<Output = Exitus<E, A>> + Send + 'static,
{
Zio {
run: Box::new(move |env| Box::pin(f(env))),
_phantom: PhantomData,
}
}
#[inline]
pub fn environment() -> Zio<R, E, R>
where
R: Clone + Send,
{
Zio {
run: Box::new(|env| Box::pin(async move { Exitus::successus(env.into_inner()) })),
_phantom: PhantomData,
}
}
#[inline]
pub fn environment_with<B, F>(f: F) -> Zio<R, E, B>
where
F: FnOnce(&R) -> B + Send + 'static,
B: Send + 'static,
{
Zio {
run: Box::new(move |env| Box::pin(async move { Exitus::successus(f(env.get())) })),
_phantom: PhantomData,
}
}
#[inline]
pub fn map<B, F>(self, f: F) -> Zio<R, E, B>
where
F: FnOnce(A) -> B + Send + 'static,
B: Send + 'static,
{
Zio {
run: Box::new(move |env| {
Box::pin(async move {
let result = (self.run)(env).await;
result.map(f)
})
}),
_phantom: PhantomData,
}
}
#[inline]
pub fn map_error<E2, F>(self, f: F) -> Zio<R, E2, A>
where
F: Fn(E) -> E2 + Send + Clone + 'static,
E2: 'static,
{
Zio {
run: Box::new(move |env| {
Box::pin(async move {
let result = (self.run)(env).await;
result.map_error(f)
})
}),
_phantom: PhantomData,
}
}
#[inline]
pub fn flat_map<B, F>(self, f: F) -> Zio<R, E, B>
where
F: FnOnce(A) -> Zio<R, E, B> + Send + 'static,
B: Send + 'static,
R: Clone,
{
Zio {
run: Box::new(move |env| {
Box::pin(async move {
let env_for_self = Ambitus::new(env.get().clone());
let result = (self.run)(env_for_self).await;
match result {
Exitus::Successus(a) => (f(a).run)(env).await,
Exitus::Defectio(c) => Exitus::Defectio(c),
}
})
}),
_phantom: PhantomData,
}
}
#[inline]
pub fn provide(self, env: R) -> Zio<(), E, A>
where
R: Send,
{
Zio {
run: Box::new(move |_| (self.run)(Ambitus::new(env))),
_phantom: PhantomData,
}
}
#[inline]
pub fn catch_all<F>(self, handler: F) -> Zio<R, E, A>
where
F: FnOnce(Causa<E>) -> Zio<R, E, A> + Send + 'static,
R: Clone + Send,
{
Zio {
run: Box::new(move |env| {
Box::pin(async move {
let env_for_self = Ambitus::new(env.get().clone());
let result = (self.run)(env_for_self).await;
match result {
ok @ Exitus::Successus(_) => ok,
Exitus::Defectio(c) => (handler(c).run)(env).await,
}
})
}),
_phantom: PhantomData,
}
}
#[inline]
pub fn catch<F>(self, handler: F) -> Zio<R, E, A>
where
F: FnOnce(E) -> Zio<R, E, A> + Send + 'static,
R: Clone + Send,
E: Clone,
{
Zio {
run: Box::new(move |env| {
Box::pin(async move {
let env_for_self = Ambitus::new(env.get().clone());
let result = (self.run)(env_for_self).await;
match result {
ok @ Exitus::Successus(_) => ok,
Exitus::Defectio(Causa::Defectus(e)) => (handler(e).run)(env).await,
other => other,
}
})
}),
_phantom: PhantomData,
}
}
#[inline]
pub fn ensuring<F>(self, finalizer: F) -> Zio<R, E, A>
where
F: FnOnce() -> Zio<R, E, ()> + Send + 'static,
R: Clone + Send,
{
Zio {
run: Box::new(move |env| {
Box::pin(async move {
let env_for_self = Ambitus::new(env.get().clone());
let result = (self.run)(env_for_self).await;
let _fin_result = (finalizer().run)(env).await;
result
})
}),
_phantom: PhantomData,
}
}
#[inline]
pub async fn run(self, env: R) -> Exitus<E, A> {
(self.run)(Ambitus::new(env)).await
}
}
pub type Task<E, A> = Zio<(), E, A>;
pub type Uio<R, A> = Zio<R, core::convert::Infallible, A>;
pub type UTask<A> = Zio<(), core::convert::Infallible, A>;
pub type Io<A> = Zio<(), alloc::string::String, A>;
#[inline]
pub fn succeed<R: Send + 'static, E: Send + 'static, A: Send + 'static>(a: A) -> Zio<R, E, A> {
Zio::succeed(a)
}
#[inline]
pub fn fail<R: Send + 'static, E: Send + 'static, A: Send + 'static>(e: E) -> Zio<R, E, A> {
Zio::fail(e)
}
#[inline]
pub fn environment<R: Clone + Send + 'static, E: Send + 'static>() -> Zio<R, E, R> {
Zio::<R, E, R>::environment()
}
#[inline]
pub fn from_result<R: Send + 'static, E: Send + 'static, A: Send + 'static>(
result: Result<A, E>,
) -> Zio<R, E, A> {
match result {
Ok(a) => Zio::succeed(a),
Err(e) => Zio::fail(e),
}
}
#[inline]
pub fn from_option<R: Send + 'static, A: Send + 'static>(opt: Option<A>) -> Zio<R, (), A> {
match opt {
Some(a) => Zio::succeed(a),
None => Zio::fail(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_causa_defectus() {
let causa: Causa<&str> = Causa::defectus("error");
assert!(causa.is_defectus());
assert_eq!(causa.defect(), Some(&"error"));
}
#[test]
fn test_causa_mors() {
let causa: Causa<&str> = Causa::mors("panic!");
assert!(causa.is_mors());
}
#[test]
fn test_causa_both() {
let c1: Causa<&str> = Causa::defectus("error1");
let c2: Causa<&str> = Causa::defectus("error2");
let combined = c1.both(c2);
assert_eq!(combined.defects().len(), 2);
}
#[test]
fn test_exitus_successus() {
let exit: Exitus<&str, i32> = Exitus::successus(42);
assert!(exit.is_successus());
assert_eq!(exit.successus_value(), Some(&42));
}
#[test]
fn test_exitus_defectio() {
let exit: Exitus<&str, i32> = Exitus::defectio("error");
assert!(exit.is_defectio());
assert!(
exit.causa()
.expect("defectio exit must have a causa")
.is_defectus()
);
}
#[test]
fn test_exitus_map() {
let exit: Exitus<&str, i32> = Exitus::successus(21);
let mapped = exit.map(|x| x * 2);
assert_eq!(mapped.successus_value(), Some(&42));
}
#[test]
fn test_exitus_flat_map() {
let exit: Exitus<&str, i32> = Exitus::successus(20);
let result = exit.flat_map(|x| Exitus::successus(x + 22));
assert_eq!(result.successus_value(), Some(&42));
}
#[test]
fn test_exitus_from_result() {
let ok: Exitus<&str, i32> = Exitus::from_result(Ok(42));
assert!(ok.is_successus());
let err: Exitus<&str, i32> = Exitus::from_result(Err("error"));
assert!(err.is_defectio());
}
#[test]
fn test_ambitus_new() {
let env = Ambitus::new(42);
assert_eq!(*env.get(), 42);
}
#[test]
fn test_ambitus_map() {
let env = Ambitus::new(21);
let mapped = env.map(|x| x * 2);
assert_eq!(*mapped.get(), 42);
}
}