2023-06-02

# PL Resources Everywhere

In playing around with OCaml, I’ve spent some spare time perusing more programming language resources. There are a bunch out there, especially in this era of online learning; for instance, Cornell has made some great resources available. In looking specifically for more ML-ish1 flavored ones, I came across this fun undergrad homework set. In the second problem, it asks you to implement a tiny (recursively defined) language of expressions encoding functions from the unit square to the unit interval, $$[0, 1]^2 \mapsto [0, 1]$$.

type expr = VarX
| VarY
| Sine of expr
| Cosine of expr
| Average of expr * expr
| Times of expr * expr
| Thresh of expr * expr * expr * expr


After you learn to build them up randomly, you can evaluate them across the unit square to generate pictures—have each output value encode either a greyscale value, or one of an RGB triple. Note to undergrads: please don’t cheat off me; you’ll only rob yourself of a fun learning experience.

# No Peeking!

This was a fun exercise in OCaml, but I also found it instructive to tackle it in Rust and compare. Besides the superficial differences, Rust’s concern with and ability to control memory usage is apparent. I defined the core enumeration thusly:

pub enum Expr {
X,
Y,
SinPi(Box<Expr>),
CosPi(Box<Expr>),
Mul(Box<Mul>),
Avg(Box<Avg>),
Thresh(Box<Thresh>),
}


I renamed some variants to more accurately express their intent; e.g. SinPi(expr) evaluates to $$sin(\pi \cdot$$ expr $$\!)$$. More importantly, I reworked the recursive variants to each contain (at most) a single Box (i.e. unique pointer to a heap allocated value); for instance, Expr::Mul contains a Box<Mul>, where Mul is defined as

pub struct Mul {
e1: Expr,
e2: Expr,
}


I took up this trick after learning more about it from the inimitable matklad. As the linked response says, this keeps the size of the base enum smaller. In the source code, I’ve got a property test that asserts the size of an arbitrary2 Expr is exactly 16 bytes3:

#[test]
fn size_of(e in arb_expr()) {
assert_eq!(size_of_val(&e), 16)
}


If we instead went with a more direct translation of the OCaml code, like

diff --git a/src/lib.rs b/src/lib.rs
index 3b04a74..68c600c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,9 +7,9 @@ pub enum Expr {
Y,
SinPi(Box<Expr>),
CosPi(Box<Expr>),
-    Mul(Box<Mul>),
-    Avg(Box<Avg>),
-    Thresh(Box<Thresh>),
+    Mul { e1: Box<Expr>, e2: Box<Expr> },
+    Avg { e1: Box<Expr>, e2: Box<Expr> },
+    Thresh { e1: Box<Expr, e2: Box<Expr>, e3: Box<Expr>, e4: Box<Expr> },
}


then this property test would no longer pass, as the enum needs to be large enough to contain its largest variant.

# Pretty Pics Plz

Once I coded up the library, I also put together a small executable to take in some parameters and generate greyscale or RGB pictures, similar to the original exercise. With that defined, I could run it a bunch of times using jot to generate random seeds and parallel to use multiple cores:

jot -w %i -r 20 0 1000000000 | parallel --bar ./target/release/ra -s {} -d 7 -r


Here are some gems I found combing through the PNGs:

1. ML as in OCaml and SML, not machine learning. ↩︎

2. I really like property-based testing, in this case with proptest↩︎

3. Since size_of_val returns the size of the pointed-to value in bytes, this test returns 16 on my 64-bit laptop; it will probably fail on a 32-bit architecture. Be sure to check out this awesome article for more on Rust enums. ↩︎