From b5449eceedbd5d93ea4bd22e604fd972c46eda43 Mon Sep 17 00:00:00 2001 From: Collin Williams <96917990+bluedragon1221@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:44:48 -0500 Subject: Reversible Programming, html formatting changes, and css tweaks --- reversible.html | 242 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 reversible.html (limited to 'reversible.html') diff --git a/reversible.html b/reversible.html new file mode 100644 index 0000000..378045f --- /dev/null +++ b/reversible.html @@ -0,0 +1,242 @@ + + + + + + + Reversible Programming Blew my Mind (Reversible Part 1) + + + + + + +
+
+ +

+ Okay, a few days ago I learned about Reversible Programming. + I decided to write a little Rust DSL to implement some of the stuff I learned. + In this article I walk through the code for my implementation and talk about some of the stuff you could do with + it. +

+ +
+ +

Math basis

+

So remember in math class when you learned to take the inverse of a function?

+

For example, say you have y = 3*x + 6.

+

You can take the inverse by solving for x. In this case, the inverse of our function is x = y/3 - 2.

+

The idea behind reversible programming is that there's a specific way to write your code such that it can automatically compute the inverse.

+

This has some wild implications:

+ +

+ These are just a few of the crazy things you could theoretically do with Reversible Programming. + But how does it actually work? +

+ +

Idea of the implementation

+

+ The idea behind this is that we can build small blocks of computation that we manually code the inverse logic for. + Then we use higher-level combinators to compose these small blocks together. + As long as the building-blocks are reversible and the combinators are implemented correctly, we'll have a fully reversible program. +

+ +

Foundations

+

As any Rust project starts, we'll define some shared behavior that our initial blocks of computation must implement.

+
trait Rev {
+    type Input;
+    type Output;
+
+    fn forward(&self, i: Self::Input) -> Self::Output;
+    fn reverse(&self, o: Self::Output) -> Self::Input;
+}
+ +

+ As a nice starter, let's write a block that would compute addition. + We can model this with a struct and an impl of the Rev trait. +

+
struct Addition<A> {
+    amount: A
+}
+
+impl<A> Rev for Addition<A>
+where
+    A: std::ops::Add<Output = A>
+        + std::ops::Sub<Output = A>
+        + Copy
+{
+    type Input = A;
+    type Output = A;
+
+    fn forward(&self, a: A) -> A {
+        a + self.amount
+    }
+
+    fn reverse(&self, b: A) -> A {
+        b - self.amount
+    }
+}
+ +

+ While the generics might look scary, this is just saying "the input and output can be any type that you can add and subtract with." + Honestly these are light work compared to what's coming up. +

+

Oh, and it will look nicer if we have an easy way to construct an Addition block:

+
fn plus<A>(amount: A) -> Addition<A> {
+    Addition { amount }
+}
+ +

So let's see it in action!

+
fn main() {
+    let operation = plus(5);
+
+    println!("{}", operation.forward(6)); // prints 11 (6 + 5 = 11)
+    println!("{}", operation.reverse(11)); // prints 6 (11 - 5 = 6)
+}
+ +

Obviously nothing special so far. We're just using the inverse of addition, which is subtraction, just like our + trait impl said.

+

I've implemented a few more in the source code:

+ + +

First Combinators

+

+ The first combinator we'll implement is Pipe. + This takes in two Revs and and makes one that applies them in sequential order. +

+
struct Pipe<X, Y>(X, Y);
+impl<X, Y> Rev for Pipe<X, Y>
+where
+    X: Iso,
+    Y: Iso<Input = X::Output>
+{
+    type Input = X::Input;
+    type Output = Y::Output;
+
+    fn forward(&self, a: Self::Input) -> Self::Output {
+        self.1.forward(self.0.forward(a))
+    }
+
+    fn reverse(&self, c: Self::Output) -> Self::Input {
+        self.0.reverse(self.1.reverse(c))
+    }
+}
+ +

+ Additionally, we'll implement Inverse (not to be confused with .reverse()). + This flips the forward and reverse methods. +

+
struct Inverse<X>(X);
+impl<X: Iso> Rev for Inverse<X> {
+    type Input = X::Output;
+    type Output = X::Input;
+
+    fn forward(&self, a: Self::Input) -> Self::Output {
+        self.0.reverse(a)
+    }
+
+    fn reverse(&self, b: Self::Output) -> Self::Input {
+        self.0.forward(b)
+    }
+}
+ +

+ And finally, it will look nicer if we can use a chaining syntax to apply these combinators. + We'll do that using an extension trait: +

+
trait RevExt: Rev + Sized {
+    fn then<N: Rev<Input = Self::Output>>(self, other: N) -> Pipe<Self, N> {
+        Pipe(self, other)
+    }
+
+    fn inverse(self) -> Inverse<Self> {
+        Inverse(self)
+    }
+}
+impl<T: Rev> RevExt for T {}
+ +

Playing around

+

+ So, what can we do with these combinators? + Quite a lot, actually! + Our math example from the beginning is trivial now: +

+
fn main() {
+    let y = times(3).then(plus(6));
+    let x = y.inverse(); // We don't have to know how this gets computed!
+
+    println!("{}", y.forward(8)); // prints 30
+    println!("{}", x.forward(30)); // prints 8
+}
+ +

Here's a little celsius to farenheit calculator that only encodes the transformation one way:

+
fn main() {
+    // F = (C * 1.8) + 32
+    let celsius_to_fahrenheit = times(1.8).then(add(32.0));
+
+    println!("{}", celsius_to_fahrenheit.forward(0.0));   // prints 32.0
+    println!("{}", celsius_to_fahrenheit.forward(100.0)); // prints 212.0
+
+    println!("{}", celsius_to_fahrenheit.reverse(212.0)); // prints 100.0
+
+    let fahrenheit_to_celsius = celsius_to_fahrenheit.inverse();
+    println!("{}", fahrenheit_to_celsius.forward(32.0));  // prints 0.0
+}
+ +

Under

+

+ under is a higher order combinator, which means it's implemented in terms of other combinators. + Under basically says "do a thing, do another thing, then undo the first thing. + With this foundation built up, we can implement it quite easily: +

+
fn under<Op, Val>(op: Op, val: Val) -> impl Rev<
+    Input = Op::Input,
+    Output = Op::Input
+>
+where
+    Op: Rev + Clone,
+    Val: Rev<Input = Op::Output, Output = Op::Output>
+{
+    op.clone().then(val).then(op.inverse())
+}
+ +

+ We can use this to encode "setup operations." + For example, this code takes in a number string, and returns a number string, but does an operation on integers in-between. +

+
fn main() {
+    let num_as_str = discrete(vec![
+        ("one", 1),
+        ("two", 2),
+        ("three", 3),
+        ("four", 4),
+        ("five", 5),
+        ("six", 6)
+    ]);
+
+    let operation = under(num_as_str, add(3));
+
+    println!("{}", operation.forward("three")); // prints "six"
+    println!("{}", operation.reverse("five")); // prints "two"
+}
+ +

Next Up

+

+ Well, that's all I've got for today. + Next time, we'll look at data-structures, like sum and product types, and maybe some other stuff. + The source code for this project so far is available here. + Stay tuned! +

+
+ + + \ No newline at end of file -- cgit v1.3.1