use alloc::vec::Vec;
use crate::typeclasses::Compositio;
use crate::typeclasses::Unitas;
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Scriptor<W, A> {
log: W,
value: A,
}
impl<W: Unitas + Clone, A> Scriptor<W, A> {
#[inline]
pub const fn new(log: W, value: A) -> Self {
Scriptor { log, value }
}
#[inline]
pub const fn dictum(log: W) -> Scriptor<W, ()> {
Scriptor::new(log, ())
}
#[inline]
pub const fn tell(log: W) -> Scriptor<W, ()> {
Self::dictum(log)
}
#[inline]
pub fn run(self) -> (W, A) {
(self.log, self.value)
}
#[inline]
pub fn unwrap(self) -> A {
self.value
}
#[inline]
pub fn pure(value: A) -> Self {
Self::new(W::empty(), value)
}
#[inline]
pub fn log(self) -> W {
self.log
}
#[inline]
pub fn value_ref(&self) -> &A {
&self.value
}
#[inline]
pub fn log_ref(&self) -> &W {
&self.log
}
#[inline]
pub fn map<B, F>(self, f: F) -> Scriptor<W, B>
where
F: FnOnce(A) -> B,
{
Scriptor {
log: self.log,
value: f(self.value),
}
}
#[inline]
pub fn flat_map<B, F>(self, f: F) -> Scriptor<W, B>
where
F: FnOnce(A) -> Scriptor<W, B>,
{
let Scriptor { log, value } = f(self.value);
Scriptor {
log: self.log.combine(&log),
value,
}
}
#[inline]
pub fn bind<B, F>(self, f: F) -> Scriptor<W, B>
where
F: FnOnce(A) -> Scriptor<W, B>,
{
self.flat_map(f)
}
#[inline]
pub fn apply<B, F>(self, wf: Scriptor<W, F>) -> Scriptor<W, B>
where
F: FnOnce(A) -> B,
{
Scriptor {
log: wf.log.combine(&self.log),
value: (wf.value)(self.value),
}
}
#[inline]
pub fn ausculta(self) -> Scriptor<W, (A, W)>
where
W: Clone,
{
let log_clone = self.log.clone();
Scriptor {
log: self.log,
value: (self.value, log_clone),
}
}
#[inline]
pub fn listen(self) -> Scriptor<W, (A, W)>
where
W: Clone,
{
self.ausculta()
}
#[inline]
pub fn censor<F>(self, f: F) -> Scriptor<W, A>
where
F: FnOnce(W) -> W,
{
Scriptor {
log: f(self.log),
value: self.value,
}
}
}
impl<W: Unitas + Clone, A: Clone + Unitas> Compositio for Scriptor<W, A> {
#[inline]
fn combine(&self, other: &Self) -> Self {
Scriptor {
log: self.log.combine(&other.log),
value: self.value.combine(&other.value),
}
}
}
impl<W: Unitas + Clone, A: Clone + Unitas> Unitas for Scriptor<W, A> {
#[inline]
fn empty() -> Self {
Scriptor {
log: W::empty(),
value: A::empty(),
}
}
}
impl<W, A> IntoIterator for Scriptor<W, A> {
type Item = A;
type IntoIter = core::option::IntoIter<A>;
fn into_iter(self) -> Self::IntoIter {
Some(self.value).into_iter()
}
}
pub type LogScriptor<A> = Scriptor<Vec<alloc::string::String>, A>;
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::{String, ToString};
use alloc::vec;
#[test]
fn test_scriptor_new() {
let writer: Scriptor<Vec<String>, i32> = Scriptor::new(vec!["test".to_string()], 42);
let (log, value) = writer.run();
assert_eq!(value, 42);
assert_eq!(log, vec!["test".to_string()]);
}
#[test]
fn test_scriptor_pure() {
let writer: Scriptor<Vec<String>, i32> = Scriptor::pure(42);
let (log, value) = writer.run();
assert_eq!(value, 42);
assert!(log.is_empty());
}
#[test]
fn test_scriptor_dictum() {
let writer: Scriptor<Vec<String>, ()> =
Scriptor::<Vec<String>, ()>::dictum(vec!["message".to_string()]);
let (log, value) = writer.run();
assert_eq!(value, ());
assert_eq!(log, vec!["message".to_string()]);
}
#[test]
fn test_scriptor_map() {
let writer = Scriptor::new(vec!["start".to_string()], 21);
let doubled = writer.map(|x| x * 2);
let (log, value) = doubled.run();
assert_eq!(value, 42);
assert_eq!(log, vec!["start".to_string()]);
}
#[test]
fn test_scriptor_flat_map() {
let writer = Scriptor::new(vec!["step1".to_string()], 10);
let result = writer.flat_map(|x| Scriptor::new(vec!["step2".to_string()], x + 5));
let (log, value) = result.run();
assert_eq!(value, 15);
assert_eq!(log, vec!["step1".to_string(), "step2".to_string()]);
}
#[test]
fn test_scriptor_chain() {
let result = Scriptor::new(vec!["A".to_string()], 1)
.flat_map(|a| Scriptor::new(vec!["B".to_string()], a + 1))
.flat_map(|b| Scriptor::new(vec!["C".to_string()], b + 1));
let (log, value) = result.run();
assert_eq!(value, 3);
assert_eq!(log, vec!["A".to_string(), "B".to_string(), "C".to_string()]);
}
#[test]
fn test_scriptor_listen() {
let writer = Scriptor::new(vec!["log".to_string()], 42);
let listened = writer.listen();
let (outer_log, (value, inner_log)) = listened.run();
assert_eq!(value, 42);
assert_eq!(inner_log, vec!["log".to_string()]);
assert_eq!(outer_log, vec!["log".to_string()]);
}
#[test]
fn test_scriptor_censor() {
let writer = Scriptor::new(vec!["hello".to_string()], 42);
let censored = writer.censor(|log| log.into_iter().map(|s| s.to_uppercase()).collect());
let (log, value) = censored.run();
assert_eq!(value, 42);
assert_eq!(log, vec!["HELLO".to_string()]);
}
#[test]
fn test_scriptor_left_identity() {
let a = 5;
let f = |x: i32| Scriptor::new(vec!["f".to_string()], x * 2);
let left: Scriptor<Vec<String>, _> = Scriptor::pure(a).bind(f);
let right = f(a);
assert_eq!(left.run(), right.run());
}
#[test]
fn test_scriptor_right_identity() {
let m = Scriptor::new(vec!["m".to_string()], 42);
let bound = m.clone().bind(Scriptor::pure);
assert_eq!(m.run(), bound.run());
}
#[test]
fn test_scriptor_associativity() {
let m = Scriptor::new(vec!["m".to_string()], 5);
let f = |x: i32| Scriptor::new(vec!["f".to_string()], x + 1);
let g = |x: i32| Scriptor::new(vec!["g".to_string()], x * 2);
let left = m.clone().bind(f).bind(g);
let right = m.bind(|x| f(x).bind(g));
assert_eq!(left.run(), right.run());
}
}