#[cfg(feature = "std")]
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use crate::nexus::effect::{Eff, EffectMarker};
use crate::nexus::row::{IO_BIT, Row};
#[derive(Copy, Clone, Debug)]
pub struct IoEffect;
impl EffectMarker for IoEffect {
const BIT: u128 = IO_BIT;
const NAME: &'static str = "IO";
}
pub type IoRow = Row<IO_BIT>;
pub enum IoOp<A> {
Read,
Write(A),
Perform(Box<dyn FnOnce() -> A>),
}
pub fn io_perform<A: 'static, F: FnOnce() -> A + 'static>(_f: F) -> Eff<IoRow, A> {
Eff::lazy(|| crate::cold_panic!("io_perform requires IO handler"))
}
#[must_use = "IO computations do nothing unless run"]
#[repr(u8)]
pub enum IoComputation<A> {
Pure(A),
Perform(Box<dyn FnOnce() -> A>),
}
impl<A: 'static> IoComputation<A> {
#[inline(always)]
pub fn new<F: FnOnce() -> A + 'static>(f: F) -> Self {
IoComputation::Perform(Box::new(f))
}
#[inline(always)]
pub fn run(self) -> A {
match self {
IoComputation::Pure(a) => a,
IoComputation::Perform(f) => f(),
}
}
#[inline(always)]
pub fn pure(value: A) -> Self {
IoComputation::Pure(value)
}
#[inline(always)]
pub fn map<B: 'static, F: FnOnce(A) -> B + 'static>(self, f: F) -> IoComputation<B> {
match self {
IoComputation::Pure(a) => IoComputation::Pure(f(a)),
IoComputation::Perform(run_fn) => IoComputation::Perform(Box::new(move || f(run_fn()))),
}
}
#[inline(always)]
pub fn and_then<B: 'static, F: FnOnce(A) -> IoComputation<B> + 'static>(
self,
f: F,
) -> IoComputation<B> {
IoComputation::Perform(Box::new(move || {
let a = self.run();
f(a).run()
}))
}
#[inline(always)]
pub fn then<B: 'static>(self, next: IoComputation<B>) -> IoComputation<B> {
self.and_then(move |_| next)
}
#[inline(always)]
pub fn before<B: 'static>(self, next: IoComputation<B>) -> IoComputation<A> {
self.and_then(move |a| next.map(move |_| a))
}
}
#[cfg(feature = "std")]
pub fn print_line(msg: alloc::string::String) -> IoComputation<()> {
IoComputation::new(move || {
std::println!("{msg}");
})
}
#[cfg(feature = "std")]
pub fn read_line() -> IoComputation<alloc::string::String> {
IoComputation::new(|| {
let mut input = alloc::string::String::new();
match std::io::stdin().read_line(&mut input) {
Ok(_) => input.trim().to_owned(),
Err(_) => alloc::string::String::new(),
}
})
}
#[cfg(feature = "std")]
pub fn delay(millis: u64) -> IoComputation<()> {
IoComputation::new(move || {
std::thread::sleep(std::time::Duration::from_millis(millis));
})
}
#[cfg(feature = "std")]
pub fn current_time_millis() -> IoComputation<u128> {
IoComputation::new(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_millis())
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_io_pure() {
let comp = IoComputation::pure(42);
assert_eq!(comp.run(), 42);
}
#[test]
fn test_io_map() {
let comp = IoComputation::pure(21).map(|x| x * 2);
assert_eq!(comp.run(), 42);
}
#[test]
fn test_io_and_then() {
let comp = IoComputation::pure(20).and_then(|x| IoComputation::pure(x + 22));
assert_eq!(comp.run(), 42);
}
#[test]
fn test_io_then() {
let comp = IoComputation::pure(1).then(IoComputation::pure(42));
assert_eq!(comp.run(), 42);
}
#[test]
fn test_io_before() {
let comp = IoComputation::pure(42).before(IoComputation::pure(0));
assert_eq!(comp.run(), 42);
}
#[test]
fn test_io_side_effect() {
use alloc::rc::Rc;
use core::cell::Cell;
let counter = Rc::new(Cell::new(0));
let counter_clone = counter.clone();
let comp = IoComputation::new(move || {
counter_clone.set(counter_clone.get() + 1);
42
});
assert_eq!(counter.get(), 0);
let result = comp.run();
assert_eq!(result, 42);
assert_eq!(counter.get(), 1);
}
#[test]
fn test_io_chain_side_effects() {
use alloc::rc::Rc;
use core::cell::Cell;
let counter = Rc::new(Cell::new(0));
let counter1 = counter.clone();
let counter2 = counter.clone();
let comp = IoComputation::new(move || {
counter1.set(counter1.get() + 1);
10
})
.and_then(move |x| {
IoComputation::new(move || {
counter2.set(counter2.get() + x);
counter2.get()
})
});
let result = comp.run();
assert_eq!(result, 11); }
}