use core::marker::PhantomData;
pub trait Profunctor<A, B>: Sized {
type Mapped<C, D>: Profunctor<C, D>;
fn dimap<C, D, F, G>(self, f: F, g: G) -> Self::Mapped<C, D>
where
F: Fn(C) -> A + 'static,
G: Fn(B) -> D + 'static;
#[inline]
fn lmap<C, F>(self, f: F) -> Self::Mapped<C, B>
where
F: Fn(C) -> A + 'static,
{
self.dimap(f, |b| b)
}
#[inline]
fn rmap<D, G>(self, g: G) -> Self::Mapped<A, D>
where
G: Fn(B) -> D + 'static,
{
self.dimap(|a| a, g)
}
}
pub trait Fortis<A, B>: Profunctor<A, B> {
type Primus<C>: Fortis<(A, C), (B, C)>;
fn primus<C>(self) -> Self::Primus<C>;
type Secundus<C>: Fortis<(C, A), (C, B)>;
fn secundus<C>(self) -> Self::Secundus<C>;
}
pub trait Electio<A, B>: Profunctor<A, B> {
type Sinister<C>: Electio<Result<A, C>, Result<B, C>>;
fn sinister<C>(self) -> Self::Sinister<C>;
type Dexter<C>: Electio<Result<C, A>, Result<C, B>>;
fn dexter<C>(self) -> Self::Dexter<C>;
}
pub struct FunctioProf<A, B, F>
where
F: Fn(A) -> B,
{
f: F,
_phantom: PhantomData<fn(A) -> B>,
}
impl<A, B, F> FunctioProf<A, B, F>
where
F: Fn(A) -> B,
{
#[inline]
pub fn new(f: F) -> Self {
FunctioProf {
f,
_phantom: PhantomData,
}
}
#[inline]
pub fn apply(&self, a: A) -> B {
(self.f)(a)
}
}
pub trait OpticumProfunctor<S, T, A, B> {
fn run<P>(&self, pab: P) -> P::Mapped<S, T>
where
P: Profunctor<A, B>;
}
pub struct AequivalentiaProfunctor<S, T, A, B, Fwd, Bwd>
where
Fwd: Fn(S) -> A,
Bwd: Fn(B) -> T,
{
forward: Fwd,
backward: Bwd,
_phantom: PhantomData<fn(S, T, A, B)>,
}
impl<S, T, A, B, Fwd, Bwd> AequivalentiaProfunctor<S, T, A, B, Fwd, Bwd>
where
Fwd: Fn(S) -> A,
Bwd: Fn(B) -> T,
{
#[inline]
pub fn new(forward: Fwd, backward: Bwd) -> Self {
AequivalentiaProfunctor {
forward,
backward,
_phantom: PhantomData,
}
}
#[inline]
pub fn forward(&self) -> &Fwd {
&self.forward
}
#[inline]
pub fn backward(&self) -> &Bwd {
&self.backward
}
}
impl<S, T, A, B, Fwd, Bwd> Clone for AequivalentiaProfunctor<S, T, A, B, Fwd, Bwd>
where
Fwd: Fn(S) -> A + Clone,
Bwd: Fn(B) -> T + Clone,
{
fn clone(&self) -> Self {
AequivalentiaProfunctor {
forward: self.forward.clone(),
backward: self.backward.clone(),
_phantom: PhantomData,
}
}
}
pub struct AspectusProfunctor<S, T, A, B, Get, Set>
where
Get: Fn(&S) -> A,
Set: Fn(S, B) -> T,
{
get: Get,
set: Set,
_phantom: PhantomData<fn(S, T, A, B)>,
}
impl<S, T, A, B, Get, Set> AspectusProfunctor<S, T, A, B, Get, Set>
where
Get: Fn(&S) -> A,
Set: Fn(S, B) -> T,
{
#[inline]
pub fn new(get: Get, set: Set) -> Self {
AspectusProfunctor {
get,
set,
_phantom: PhantomData,
}
}
#[inline]
pub fn view(&self, s: &S) -> A {
(self.get)(s)
}
#[inline]
pub fn set(&self, s: S, b: B) -> T {
(self.set)(s, b)
}
#[inline]
pub fn over<F>(&self, s: S, f: F) -> T
where
F: FnOnce(A) -> B,
{
let a = (self.get)(&s);
(self.set)(s, f(a))
}
}
impl<S, T, A, B, Get, Set> Clone for AspectusProfunctor<S, T, A, B, Get, Set>
where
Get: Fn(&S) -> A + Clone,
Set: Fn(S, B) -> T + Clone,
{
fn clone(&self) -> Self {
AspectusProfunctor {
get: self.get.clone(),
set: self.set.clone(),
_phantom: PhantomData,
}
}
}
pub struct DivisioProfunctor<S, T, A, B, Match, Build>
where
Match: Fn(S) -> Result<A, T>,
Build: Fn(B) -> T,
{
matching: Match,
build: Build,
_phantom: PhantomData<fn(S, T, A, B)>,
}
impl<S, T, A, B, Match, Build> DivisioProfunctor<S, T, A, B, Match, Build>
where
Match: Fn(S) -> Result<A, T>,
Build: Fn(B) -> T,
{
#[inline]
pub fn new(matching: Match, build: Build) -> Self {
DivisioProfunctor {
matching,
build,
_phantom: PhantomData,
}
}
#[inline]
pub fn preview(&self, s: S) -> Option<A> {
(self.matching)(s).ok()
}
#[inline]
pub fn review(&self, b: B) -> T {
(self.build)(b)
}
#[inline]
pub fn over<F>(&self, s: S, f: F) -> T
where
F: FnOnce(A) -> B,
{
match (self.matching)(s) {
Ok(a) => (self.build)(f(a)),
Err(t) => t,
}
}
}
impl<S, T, A, B, Match, Build> Clone for DivisioProfunctor<S, T, A, B, Match, Build>
where
Match: Fn(S) -> Result<A, T> + Clone,
Build: Fn(B) -> T + Clone,
{
fn clone(&self) -> Self {
DivisioProfunctor {
matching: self.matching.clone(),
build: self.build.clone(),
_phantom: PhantomData,
}
}
}
pub type AequivalentiaSimplexProf<S, A, Fwd, Bwd> = AequivalentiaProfunctor<S, S, A, A, Fwd, Bwd>;
pub type AspectusSimplexProf<S, A, Get, Set> = AspectusProfunctor<S, S, A, A, Get, Set>;
pub type DivisioSimplexProf<S, A, Match, Build> = DivisioProfunctor<S, S, A, A, Match, Build>;
type ComposedOpticMarker<S, T, M, N, A, B> = PhantomData<fn(S, T, M, N, A, B)>;
pub struct ComposedOptic<O1, O2, S, T, M, N, A, B>
where
O1: Clone,
O2: Clone,
{
outer: O1,
inner: O2,
_phantom: ComposedOpticMarker<S, T, M, N, A, B>,
}
impl<O1, O2, S, T, M, N, A, B> ComposedOptic<O1, O2, S, T, M, N, A, B>
where
O1: Clone,
O2: Clone,
{
#[inline]
pub fn new(outer: O1, inner: O2) -> Self {
ComposedOptic {
outer,
inner,
_phantom: PhantomData,
}
}
}
impl<O1, O2, S, T, M, N, A, B> Clone for ComposedOptic<O1, O2, S, T, M, N, A, B>
where
O1: Clone,
O2: Clone,
{
fn clone(&self) -> Self {
ComposedOptic {
outer: self.outer.clone(),
inner: self.inner.clone(),
_phantom: PhantomData,
}
}
}
#[inline]
pub fn aspectus_profunctor<S, A, Get, Set>(
get: Get,
set: Set,
) -> AspectusSimplexProf<S, A, Get, Set>
where
Get: Fn(&S) -> A,
Set: Fn(S, A) -> S,
{
AspectusProfunctor::new(get, set)
}
#[inline]
pub fn aspectus_profunctor_poly<S, T, A, B, Get, Set>(
get: Get,
set: Set,
) -> AspectusProfunctor<S, T, A, B, Get, Set>
where
Get: Fn(&S) -> A,
Set: Fn(S, B) -> T,
{
AspectusProfunctor::new(get, set)
}
#[inline]
pub fn divisio_profunctor<S, A, Match, Build>(
matching: Match,
build: Build,
) -> DivisioSimplexProf<S, A, Match, Build>
where
Match: Fn(S) -> Result<A, S>,
Build: Fn(A) -> S,
{
DivisioProfunctor::new(matching, build)
}
#[inline]
pub fn divisio_profunctor_poly<S, T, A, B, Match, Build>(
matching: Match,
build: Build,
) -> DivisioProfunctor<S, T, A, B, Match, Build>
where
Match: Fn(S) -> Result<A, T>,
Build: Fn(B) -> T,
{
DivisioProfunctor::new(matching, build)
}
#[inline]
pub fn aequivalentia_profunctor<S, A, Fwd, Bwd>(
forward: Fwd,
backward: Bwd,
) -> AequivalentiaSimplexProf<S, A, Fwd, Bwd>
where
Fwd: Fn(S) -> A,
Bwd: Fn(A) -> S,
{
AequivalentiaProfunctor::new(forward, backward)
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::string::{String, ToString};
#[derive(Clone, Debug, PartialEq)]
struct Person {
name: String,
age: u32,
}
#[derive(Clone, Debug, PartialEq)]
enum Shape {
Circle(f64),
Rectangle(f64, f64),
}
#[test]
fn test_aspectus_profunctor_view() {
let name_lens = aspectus_profunctor(
|p: &Person| p.name.clone(),
|p: Person, name| Person { name, age: p.age },
);
let person = Person {
name: "Alice".to_string(),
age: 30,
};
assert_eq!(name_lens.view(&person), "Alice");
}
#[test]
fn test_aspectus_profunctor_set() {
let name_lens = aspectus_profunctor(
|p: &Person| p.name.clone(),
|p: Person, name| Person { name, age: p.age },
);
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let updated = name_lens.set(person, "Bob".to_string());
assert_eq!(updated.name, "Bob");
assert_eq!(updated.age, 30);
}
#[test]
fn test_aspectus_profunctor_over() {
let name_lens = aspectus_profunctor(
|p: &Person| p.name.clone(),
|p: Person, name| Person { name, age: p.age },
);
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let modified = name_lens.over(person, |n| n.to_uppercase());
assert_eq!(modified.name, "ALICE");
}
#[test]
fn test_divisio_profunctor_preview() {
let circle_prism = divisio_profunctor(
|s: Shape| match s {
Shape::Circle(r) => Ok(r),
other => Err(other),
},
Shape::Circle,
);
let circle = Shape::Circle(5.0);
let rect = Shape::Rectangle(3.0, 4.0);
assert_eq!(circle_prism.preview(circle), Some(5.0));
assert_eq!(circle_prism.preview(rect), None);
}
#[test]
fn test_divisio_profunctor_review() {
let circle_prism = divisio_profunctor(
|s: Shape| match s {
Shape::Circle(r) => Ok(r),
other => Err(other),
},
Shape::Circle,
);
assert_eq!(circle_prism.review(10.0), Shape::Circle(10.0));
}
#[test]
fn test_divisio_profunctor_over() {
let circle_prism = divisio_profunctor(
|s: Shape| match s {
Shape::Circle(r) => Ok(r),
other => Err(other),
},
Shape::Circle,
);
let circle = Shape::Circle(5.0);
let rect = Shape::Rectangle(3.0, 4.0);
let doubled = circle_prism.over(circle, |r| r * 2.0);
assert_eq!(doubled, Shape::Circle(10.0));
let unchanged = circle_prism.over(rect.clone(), |r| r * 2.0);
assert_eq!(unchanged, rect);
}
#[test]
fn test_aequivalentia_profunctor() {
let swap_iso = aequivalentia_profunctor(
|(a, b): (i32, String)| (b, a),
|(b, a): (String, i32)| (a, b),
);
let original = (42, "hello".to_string());
let swapped = (swap_iso.forward())(original.clone());
assert_eq!(swapped, ("hello".to_string(), 42));
let back = (swap_iso.backward())(swapped);
assert_eq!(back, original);
}
#[test]
fn test_polymorphic_lens() {
#[derive(Clone, Debug, PartialEq)]
struct PersonWithNickname {
name: String,
nickname: String,
age: u32,
}
let poly_lens = aspectus_profunctor_poly(
|p: &Person| p.name.clone(),
|p: Person, nickname: String| PersonWithNickname {
name: p.name,
nickname,
age: p.age,
},
);
let person = Person {
name: "Alice".to_string(),
age: 30,
};
let with_nickname = poly_lens.set(person, "Ali".to_string());
assert_eq!(with_nickname.name, "Alice");
assert_eq!(with_nickname.nickname, "Ali");
assert_eq!(with_nickname.age, 30);
}
}