#![cfg(feature = "optics")]
use prismqueer::optics::fold::Fold;
use prismqueer::optics::gather::{AddGather, ConcatGather, Gather};
use prismqueer::optics::iso::Iso;
use prismqueer::optics::lens::Lens;
use prismqueer::optics::monoid::PrismMonoid;
use prismqueer::optics::optic_prism::OpticPrism;
use prismqueer::optics::setter::Setter;
use prismqueer::optics::traversal::Traversal;
use prismqueer::{Beam, Optic, Prism};
use prismqueer::{Loss, ScalarLoss};
use std::convert::Infallible;
fn seed<T: Clone>(v: T) -> Optic<(), T, Infallible, ScalarLoss> {
Optic::ok((), v)
}
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_full_pipeline_round_trips() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let beam = seed("hello".to_string());
let focused = iso.focus(beam);
let projected = iso.project(focused);
let refracted = iso.settle(projected);
assert_eq!(refracted.result().ok(), Some(&"hello".to_string()));
}
#[derive(Clone, Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn point_view_x(p: &Point) -> i32 {
p.x
}
fn point_set_x(p: Point, new_x: i32) -> Point {
Point { x: new_x, ..p }
}
#[test]
fn lens_pipeline_extracts_field() {
let x_lens: Lens<Point, i32> = Lens::new(point_view_x, point_set_x);
let focused = x_lens.focus(seed(Point { x: 42, y: 7 }));
let projected = x_lens.project(focused);
let refracted = x_lens.settle(projected);
assert_eq!(refracted.result().ok(), Some(&42));
}
#[derive(Clone, Debug, PartialEq)]
enum Shape {
Circle(i32),
Square(i32),
}
fn is_circle(s: &Shape) -> bool {
matches!(s, Shape::Circle(_))
}
fn extract_circle(s: &Shape) -> i32 {
if let Shape::Circle(r) = s {
*r
} else {
-1
}
}
fn review_circle(r: i32) -> Shape {
Shape::Circle(r)
}
#[test]
fn optic_prism_nonmatch_carries_infinite_loss() {
let p: OpticPrism<Shape, i32> = OpticPrism::new(is_circle, extract_circle, review_circle);
let focused = p.focus(seed(Shape::Square(3)));
assert!(focused.is_partial());
}
#[test]
fn traversal_with_gather_via_smap() {
fn double(x: i32) -> i32 {
x * 2
}
let t: Traversal<i32, i32> = Traversal::new(double);
let focused = t.focus(seed(vec![1, 2, 3]));
let gathered = focused.smap(|v| {
let result = AddGather.gather(v.clone());
terni::Imperfect::Success(result)
});
assert_eq!(gathered.result().ok(), Some(&12));
}
#[derive(Clone)]
struct Tree {
leaves: Vec<i32>,
}
fn tree_leaves(t: &Tree) -> Vec<i32> {
t.leaves.clone()
}
#[test]
fn fold_extracts_and_gathers() {
let f: Fold<Tree, i32> = Fold::new(tree_leaves);
let focused = f.focus(seed(Tree {
leaves: vec![10, 20, 30],
}));
let gathered = focused.smap(|v| terni::Imperfect::Success(AddGather.gather(v.clone())));
assert_eq!(gathered.result().ok(), Some(&60));
}
#[derive(Clone, Debug, PartialEq)]
struct Box2 {
label: String,
count: i32,
}
fn box2_modify_count(b: Box2, f: &dyn Fn(i32) -> i32) -> Box2 {
Box2 {
count: f(b.count),
..b
}
}
#[test]
fn setter_pipeline_preserves_value() {
let s: Setter<Box2, i32> = Setter::new(box2_modify_count);
let b = Box2 {
label: "test".to_string(),
count: 5,
};
let focused = s.focus(seed(b.clone()));
let projected = s.project(focused);
let refracted = s.settle(projected);
assert_eq!(refracted.result().ok(), Some(&b));
}
#[test]
fn concat_gather_collapses_strings() {
let result = ConcatGather.gather(vec![
"hello".to_string(),
" ".to_string(),
"world".to_string(),
]);
assert_eq!(result, "hello world");
}
#[test]
fn monoid_laws_hold() {
use prismqueer::optics::monoid::CountMonoid;
let a = CountMonoid::new(1);
let b = CountMonoid::new(2);
let c = CountMonoid::new(3);
let left = a.clone().compose(b.clone()).compose(c.clone());
let right = a.compose(b.compose(c));
assert_eq!(left.count(), right.count());
}
fn wrap_success_i32(v: &i32) -> terni::Imperfect<i32, String, ScalarLoss> {
terni::Imperfect::Success(*v)
}
#[test]
fn smap_on_err_propagates_dark_in_integration() {
let b: Optic<(), i32, String, ScalarLoss> = Optic::err((), "fail".into());
let n = b.smap(wrap_success_i32);
assert!(n.is_err());
assert_eq!(n.result().err(), Some(&"fail".to_string()));
}
#[test]
fn smap_fn_ptr_executes_in_integration() {
let b: Optic<(), i32, String, ScalarLoss> = Optic::ok((), 5);
let n = b.smap(wrap_success_i32);
assert_eq!(n.result().ok(), Some(&5));
}
#[test]
fn next_on_err_propagates_dark_in_integration() {
let b: Optic<(), i32, String, ScalarLoss> = Optic::err((), "fail".into());
let n = b.next(99i32);
assert!(n.is_err());
assert_eq!(n.result().err(), Some(&"fail".to_string()));
}
#[test]
fn tick_partial_to_partial_accumulates_loss_in_integration() {
let b: Optic<(), i32, String, ScalarLoss> = Optic::partial((), 1i32, ScalarLoss::new(1.0));
let n = b.tick(terni::Imperfect::<i32, String, ScalarLoss>::Partial(
2,
ScalarLoss::new(0.5),
));
assert!(n.is_partial());
assert_eq!(n.result().loss().as_f64(), 1.5);
}
#[test]
fn tick_partial_to_failure_in_integration() {
let b: Optic<(), i32, String, ScalarLoss> = Optic::partial((), 1i32, ScalarLoss::new(1.0));
let n = b.tick(terni::Imperfect::<i32, String, ScalarLoss>::Failure(
"e".into(),
ScalarLoss::zero(),
));
assert!(n.is_err());
}
#[test]
fn optic_prism_review_covers_review_fn() {
let p: OpticPrism<Shape, i32> = OpticPrism::new(is_circle, extract_circle, review_circle);
let s = p.review(7);
assert_eq!(s, Shape::Circle(7));
}
#[test]
fn optic_prism_matching_focus_calls_next() {
let p: OpticPrism<Shape, i32> = OpticPrism::new(is_circle, extract_circle, review_circle);
let focused = p.focus(seed(Shape::Circle(42)));
assert!(focused.is_ok());
assert_eq!(focused.result().ok(), Some(&42i32));
}
#[test]
fn lens_set_covers_setter_fn() {
let x_lens: Lens<Point, i32> = Lens::new(point_view_x, point_set_x);
let p = Point { x: 1, y: 2 };
let updated = x_lens.set(p, 99);
assert_eq!(updated.x, 99);
assert_eq!(updated.y, 2);
}
#[test]
fn count_monoid_identity_in_integration() {
use prismqueer::optics::monoid::{CountMonoid, PrismMonoid};
let id = CountMonoid::identity();
let a = CountMonoid::new(3);
assert_eq!(id.compose(a.clone()).count(), a.count());
}
#[test]
fn loss_propagation_through_optic_pipeline() {
let iso: Iso<String, Vec<char>> = Iso::new(str_to_chars, chars_to_str);
let beam: Optic<(), String, Infallible, ScalarLoss> =
Optic::partial((), "hi".to_string(), ScalarLoss::new(0.5));
let focused = iso.focus(beam);
assert!(focused.is_partial(), "loss must propagate through focus");
let projected = iso.project(focused);
assert!(
projected.is_partial(),
"loss must propagate through project"
);
let refracted = iso.settle(projected);
assert!(refracted.is_partial(), "loss must propagate through settle");
}