use crate::ScalarLoss;
use crate::{Beam, Optic, Prism};
use std::convert::Infallible;
#[derive(Clone, Copy)]
pub struct Iso<A, B> {
forward_fn: fn(A) -> B,
backward_fn: fn(B) -> A,
}
impl<A: 'static, B: 'static> Iso<A, B> {
pub fn new(forward: fn(A) -> B, backward: fn(B) -> A) -> Self {
Iso {
forward_fn: forward,
backward_fn: backward,
}
}
pub fn forward(&self, a: A) -> B {
(self.forward_fn)(a)
}
pub fn backward(&self, b: B) -> A {
(self.backward_fn)(b)
}
}
impl<A: Clone + 'static, B: Clone + 'static> Prism for Iso<A, B> {
type Input = Optic<(), A, Infallible, ScalarLoss>;
type Focused = Optic<A, B, Infallible, ScalarLoss>;
type Projected = Optic<B, B, Infallible, ScalarLoss>;
type Refracted = Optic<B, A, Infallible, ScalarLoss>;
fn focus(&self, beam: Self::Input) -> Self::Focused {
let a = beam.result().ok().expect("focus: Err beam").clone();
let b = (self.forward_fn)(a);
beam.next(b)
}
fn project(&self, beam: Self::Focused) -> Self::Projected {
let b = beam.result().ok().expect("project: Err beam").clone();
beam.next(b)
}
fn settle(&self, beam: Self::Projected) -> Self::Refracted {
let b = beam.result().ok().expect("settle: Err beam").clone();
let a = (self.backward_fn)(b);
beam.next(a)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Beam as BeamTrait;
fn str_to_chars(s: String) -> Vec<char> {
s.chars().collect()
}
fn chars_to_str(v: Vec<char>) -> String {
v.into_iter().collect()
}
#[test]
fn iso_forward() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let result = iso.forward("hello".to_string());
assert_eq!(result, vec!['h', 'e', 'l', 'l', 'o']);
}
#[test]
fn iso_backward() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let result = iso.backward(vec!['h', 'i']);
assert_eq!(result, "hi");
}
#[test]
fn iso_round_trip_forward_backward() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let original = "hello".to_string();
let round_tripped = iso.backward(iso.forward(original.clone()));
assert_eq!(round_tripped, original);
}
#[test]
fn iso_round_trip_backward_forward() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let original = vec!['a', 'b', 'c'];
let round_tripped = iso.forward(iso.backward(original.clone()));
assert_eq!(round_tripped, original);
}
fn seed<T: Clone>(v: T) -> Optic<(), T, Infallible, ScalarLoss> {
Optic::ok((), v)
}
#[test]
fn iso_prism_focus_applies_forward() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let beam = seed("hi".to_string());
let focused = iso.focus(beam);
assert_eq!(focused.result().ok(), Some(&vec!['h', 'i']));
assert_eq!(focused.input(), &"hi".to_string());
}
#[test]
fn iso_prism_project_is_identity() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let focused = iso.focus(seed("ab".to_string()));
let projected = iso.project(focused);
assert_eq!(projected.result().ok(), Some(&vec!['a', 'b']));
}
#[test]
fn iso_prism_refract_applies_backward() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let focused = iso.focus(seed("hi".to_string()));
let projected = iso.project(focused);
let refracted = iso.settle(projected);
assert_eq!(refracted.result().ok(), Some(&"hi".to_string()));
}
#[test]
fn iso_prism_full_pipeline_round_trips() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let beam = seed("test".to_string());
let focused = iso.focus(beam);
let projected = iso.project(focused);
let refracted = iso.settle(projected);
assert_eq!(refracted.result().ok(), Some(&"test".to_string()));
assert!(refracted.is_ok());
assert!(!refracted.is_partial());
}
#[test]
fn iso_prism_is_lossless() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let focused = iso.focus(seed("x".to_string()));
assert!(!focused.is_partial());
let projected = iso.project(focused);
assert!(!projected.is_partial());
let refracted = iso.settle(projected);
assert!(!refracted.is_partial());
}
#[test]
fn iso_is_clone_and_copy() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let iso2 = iso; let iso3 = iso2.clone(); let _ = iso3.forward("ok".to_string());
}
}