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:
-
I really like property-based testing, in this case with
proptest
. ↩︎ -
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. ↩︎