[This is just a little “trip report” from my time programming in Rust. A top-level observation that I would make: OCaml programmers ought to have a look at Rust. It’ll convince you that the OCaml way of thinking is completely applicable to low-level high-performance systems code. Great stuff.]
Lately I’ve been writing a lot of code in Rust, for a project that requires high performance and multithread-capability. It also … well, it’ll tax the memory of the 6TB 32-core machine I have available (and need all the cores), so there’s that, too. In the past, I would have written such code in C++, but after 2.5 weeks of programming in Rust, I’ve changed my mind completely, and now Rust is a tool in toolbox. But that’s not the subject of this post (and question).
Instead, I’d like to focus on one particular aspect of Rust: the heavy use of “iterators” and “iterator transformation”. They seem the achieve the same things we in FP-land do with our map-then-map-then-concat style of programming, but without all the intermediate construction of lists. In short, a bit of sort-of-automatic deforestation. [ok, ok, I’m no longer a PL guy, so that’s just random brain-fart] Necessarily, this also implicates Rust’s “traits”, which are a version of typeclasses. I hope these snippets are comprehensible …
- First, some variables:
z: &Vec<bool>,
x: &Vec<bool>,
coeff: Complex64,
phase: i64,
group_phase: bool,
twos_array : Vec<u64>,
indptr : Vec<u64>
- iterate over
x
(with indices), filter out only true bools, map those indices thrutwos_array
, and add up the values.
x.iter().enumerate()
.filter(|(_,x)| **x)
.map(|(i,_)| twos_array[i])
.sum() ;
- iterate over
indptr
, checking a predicate on each value and producingcoeff
or-coeff
, then collect into a vector.
indptr.iter()
.map(|ind| {
if (ind & z_indices).count_ones() % 2 == 1 {
-coeff
}
else {
coeff
}
})
.collect() ;
There’s lots more, and much more complicated. The interesting thing to me, is that even though the implementations of these iterators and iterator-transformers are imperative, the code one writes seems amenable to referential transparency-style reasoning. Part of why, is that these mutable values are only ever owned by one owner (Rust’s type system guarantee) and so lots of things that we would do by creating new values, Rust does by modification-in-place.
This also brings in the heavy use of typeclass-like “traits”, and the way that something like
a.f1(args1).f2(args2).f3(args3)
is not compiled like
a |> f1 args1 |> f2 args2 |> f3 args3
(that is, f3 args3 (f2 args2 (f2 args1 a))
) but rather more like
f3 (f2 (f1 a args1) args2) args3
One could imagine a bit of new syntax, |.>
, with syntax rule
a |.> f b c d
rewritten to f a b c d
, and in combination with modular implicits, maybe you could do this style of programming in OCaml. Of course, without the “one owner” typing rules.
OK, so that was a bit rambling. I throw this out, as a long-standing “value-oriented programming” aficionado bigot, who sees this new (to me) style of programming and sees great value in it. And I wonder if others see the same thing.