Frequencies Redux

tl;dr Traits and refinements are easy and make code more idiomatic.

Naturally what started off as a way to check off a day on my advent blogging escapades ended up with my brain chewing on it a little longer. Now I can check off two days!

I’ve just started dabbling with Rust, but one thing that occurred to me after writing the frequencies implementation is that it might be more idiomatically expressed using what Rust calls a “trait”. The punchline: instead of writing frequencies(things), I can write things.frequencies(). That feels more Rusty to me (but again, I’m new to the language.) There are better reasons to use traits than the sheer aesthetics of the code (reusability for one), but that’s all I’m focused on today.

use std::collections::HashMap;
use std::hash::Hash;

trait Frequencies<T> {
    fn frequencies(&self) -> HashMap<T, i32>;
}

impl<T: Copy + Eq + Hash> Frequencies<T> for Vec<T> {
    fn frequencies(&self) -> HashMap<T, i32> {
        let mut freqs = HashMap::new();
        for thing in self {
            let count = freqs.get(thing).unwrap_or(&0) + 1;
            freqs.insert(*thing, count);
        }
        freqs
    }
}

fn main() {
    let strs = vec!["a", "b", "c", "b", "a", "d", "a"];
    println!("{:?}", strs.frequencies());
}

Really, not too much more code than the original, just a wee bit o’ boilerplate to declare the trait and wrap the implementation. It seems a little weird that the type variable needs to be referenced so many times, but again, if I was a better Rust programmer I’d have an explanation. (I’m sure each and every one has a perfectly cromulent reason to be there.) For now, I’ll just complain about my poor, tired typing fingers.

As an addendum, this got me to thinking that, all things considered, I’d prefer to write things.frequencies in Ruby too. What would that look like? There’s at least a couple of ways to skin this cat. You could reach for the long-maligned monkey-patch hammer, and just add a frequencies method to Enumerable directly. Alternatively, you could reach for the oft-maligned “refinements” feature introduced in Ruby 2.1. I haven’t used them much, so it’s nice when an opportunity presents itself.

module Frequencies
  refine Enumerable do
    def frequencies
      freqs = Hash.new { |hash, key| hash[key] = 0 }
      self.each_with_object(freqs) do |thing, freqs|
        freqs[thing] += 1
      end
    end
  end
end

using Frequencies

p %w[a b c b a d a].frequencies

Again, very little extra effort to get here.

This was a pretty simple exercise (okay, getting all the generics right in Rust was tricky1). But I prefer how the code reads in both languages, and I always enjoy exploring how code feels. On top of that, it’s nice to know what the implementation cost of getting to that readability is. In Ruby and Rust, both languages make it easy for the programmer to do what they want.

Questions? Comments? Contact me!

Tools Used

Ruby
2.6.3p62
Rust
1.39.0