use crate::fold::Fold;
use crate::optic::Optic;
use crate::review::Review;
use crate::setter::Setter;
use crate::traversal::Traversal;
use karpal_profunctor::choice::Choice;
pub struct Prism<S, T, A, B> {
match_: fn(S) -> Result<A, T>,
build: fn(B) -> T,
}
pub type SimplePrism<S, A> = Prism<S, S, A, A>;
impl<S, T, A, B> Optic for Prism<S, T, A, B> {}
impl<S, T, A, B> Prism<S, T, A, B> {
pub fn new(match_: fn(S) -> Result<A, T>, build: fn(B) -> T) -> Self {
Self { match_, build }
}
pub fn preview(&self, s: &S) -> Option<A>
where
S: Clone,
{
(self.match_)(s.clone()).ok()
}
pub fn review(&self, b: B) -> T {
(self.build)(b)
}
pub fn set(&self, s: S, b: B) -> T {
match (self.match_)(s) {
Ok(_) => (self.build)(b),
Err(t) => t,
}
}
pub fn over(&self, s: S, f: impl FnOnce(A) -> B) -> T {
match (self.match_)(s) {
Ok(a) => (self.build)(f(a)),
Err(t) => t,
}
}
pub fn to_review(&self) -> Review<T, B> {
Review::new(self.build)
}
pub fn to_setter(&self) -> Setter<S, T, A, B>
where
S: 'static,
T: 'static,
A: 'static,
B: 'static,
{
let match_ = self.match_;
let build = self.build;
Setter::new(move |s: S, f: &dyn Fn(A) -> B| match match_(s) {
Ok(a) => build(f(a)),
Err(t) => t,
})
}
pub fn to_traversal(&self) -> Traversal<S, T, A, B>
where
S: Clone + 'static,
T: 'static,
A: 'static,
B: 'static,
{
let match_ = self.match_;
let build = self.build;
Traversal::new(
move |s: &S| match match_(s.clone()) {
Ok(a) => vec![a],
Err(_) => vec![],
},
move |s: S, f: &dyn Fn(A) -> B| match match_(s) {
Ok(a) => build(f(a)),
Err(t) => t,
},
)
}
pub fn to_fold(&self) -> Fold<S, A>
where
S: Clone + 'static,
T: 'static,
A: 'static,
{
let match_ = self.match_;
Fold::new(move |s: &S| match match_(s.clone()) {
Ok(a) => vec![a],
Err(_) => vec![],
})
}
pub fn transform<P: Choice>(&self, pab: P::P<A, B>) -> P::P<S, T>
where
S: 'static,
T: 'static,
A: 'static,
B: 'static,
{
let match_ = self.match_;
let build = self.build;
let right_pab = P::right::<A, B, T>(pab);
P::dimap(
move |s: S| match match_(s) {
Ok(a) => Err(a), Err(t) => Ok(t), },
move |result: Result<T, B>| match result {
Ok(t) => t, Err(b) => build(b), },
right_pab,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use karpal_profunctor::FnP;
use proptest::prelude::*;
#[derive(Debug, Clone, PartialEq)]
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
fn circle_prism() -> SimplePrism<Shape, f64> {
Prism::new(
|s| match s {
Shape::Circle(r) => Ok(r),
Shape::Rectangle(w, h) => Err(Shape::Rectangle(w, h)),
},
Shape::Circle,
)
}
fn sample_circle() -> Shape {
Shape::Circle(5.0)
}
fn sample_rect() -> Shape {
Shape::Rectangle(3.0, 4.0)
}
#[test]
fn preview_match() {
let prism = circle_prism();
assert_eq!(prism.preview(&sample_circle()), Some(5.0));
}
#[test]
fn preview_no_match() {
let prism = circle_prism();
assert_eq!(prism.preview(&sample_rect()), None);
}
#[test]
fn review() {
let prism = circle_prism();
assert_eq!(prism.review(10.0), Shape::Circle(10.0));
}
#[test]
fn set_match() {
let prism = circle_prism();
assert_eq!(prism.set(sample_circle(), 10.0), Shape::Circle(10.0));
}
#[test]
fn set_no_match() {
let prism = circle_prism();
assert_eq!(prism.set(sample_rect(), 10.0), sample_rect());
}
#[test]
fn over_match() {
let prism = circle_prism();
assert_eq!(
prism.over(sample_circle(), |r| r * 2.0),
Shape::Circle(10.0)
);
}
#[test]
fn over_no_match() {
let prism = circle_prism();
assert_eq!(prism.over(sample_rect(), |r| r * 2.0), sample_rect());
}
fn finite_f64() -> impl Strategy<Value = f64> {
(-1e6f64..1e6f64).prop_filter("finite", |v| v.is_finite())
}
proptest! {
#[test]
fn law_review_preview(b in finite_f64()) {
let prism = circle_prism();
let s = prism.review(b);
prop_assert_eq!(prism.preview(&s), Some(b));
}
}
proptest! {
#[test]
fn law_preview_review(r in finite_f64()) {
let prism = circle_prism();
let s = Shape::Circle(r);
if let Some(a) = prism.preview(&s) {
prop_assert_eq!(prism.review(a), s);
}
}
}
proptest! {
#[test]
fn law_over_identity_circle(r in finite_f64()) {
let prism = circle_prism();
let s = Shape::Circle(r);
prop_assert_eq!(prism.over(s.clone(), |x| x), s);
}
#[test]
fn law_over_identity_rect(w in finite_f64(), h in finite_f64()) {
let prism = circle_prism();
let s = Shape::Rectangle(w, h);
prop_assert_eq!(prism.over(s.clone(), |x| x), s);
}
}
#[test]
fn transform_fnp_match() {
let prism = circle_prism();
let double: Box<dyn Fn(f64) -> f64> = Box::new(|r| r * 2.0);
let transform_fn = prism.transform::<FnP>(double);
assert_eq!(transform_fn(sample_circle()), Shape::Circle(10.0));
}
#[test]
fn transform_fnp_no_match() {
let prism = circle_prism();
let double: Box<dyn Fn(f64) -> f64> = Box::new(|r| r * 2.0);
let transform_fn = prism.transform::<FnP>(double);
assert_eq!(transform_fn(sample_rect()), sample_rect());
}
}