Sync’ up! … without getting drained

jan 12

Simple slug generator

One of the often-used patterns in web development is generating a quasi-random string. These are great for creating ‘slugs’ as often seen in URL shorteners, & in this mobile-friendly age, passing around small URLs is obligatory due to the limitations of small screens & SMS.

In Erlang, we are going to write a small module called ‘slug.erl’ which can handle all of this. Our home-spun module will yield a quasi-random string of characters & with that, we can let our imagination run wild with what to do with the results.

Preppin’

Before we open an editor, let’s lay out a few of the goals for what our code should do:

  1. The API should be flexible in generating slugs of a given length
  2. For UX purposes, we don’t want any slug to contain the zero or capital oh characters — they look the same on some screens after all
  3. Our code should make a valiant effort to generate solid randomness

With that small specification in place, we should be good to take a stab at it.

Code

Our slug should incorporate a range of characters, and although there are a plethora of ways to accomplish this, there’s nothing wrong with being pedantic about it and just list them all out by hand.

With that concession made, here’s the first few lines of our new module, ‘slug.erl’ :

-module(slug).
-export([new/1]).

-define(CHARS, "ABCDEFGHIJKLMNPQRSTUVWXYZ"
               "abcdefghijklmnopqrstuvwxyz123456789").

If you take a close look, you’ll notice that our macro does not include our forbidden characters (by design, of course).

Moving along, we next need to implement new/1. First off, it will have to seed a random value; a set of integers which gets stored via rand:seed/n in the calling process’s ‘process dictionary.’ We use the ‘crypto’ module to generate the randomness, though this may be overkill for some.

To understand this, open an Erlang shell & perform the following:

1> process_info(self()). % Note the `dictionary' value
2> <<X:32, Y:32, Z:32>> = crypto:strong_rand_bytes(12).
3> rand:seed(exs1024s, {X, Y, Z}).
4> process_info(self()). % Note the change?

N.B. Older installs of OTP will want to leverage the random module, instead of the newer rand one.

From there, there’s almost nothing left to code. Here’s ‘slug.erl’ in its entirety:

-module(slug).
-export([new/1]).

-define(CHARS, "ABCDEFGHIJKLMNPQRSTUVWXYZ"
               "abcdefghijklmnopqrstuvwxyz123456789").

new(N) ->
    <<X:32, Y:32, Z:32>> = crypto:strong_rand_bytes(12),
    rand:seed(exs1024s, {X, Y, Z}),
    Cs    = list_to_tuple(?CHARS),
    CsLen = tuple_size(Cs),
    [ pick(CsLen, Cs) || _ <- lists:seq(1, N) ].

pick(K, Ts) ->
    C = rand:uniform(K),
    element(C, Ts).

Working backwards, our pick/2 routine first chooses a random value between 1 and the given argument/number, K. With that random index, it looks up the apt value from tuple Ts.

In the new/1 routine, after the familiar seeding is accomplished, we then cast our string literal from a list (more on that here) to a tuple, so we can benefit from O(1) access.

Finally, for given length N, we iterate N times to get a random string of characters from the list we have specified in our macro.

Fin

With that, we can efficiently generate random slugs for constructing identifiers for URLs, create quasi-unique IDs for database records, or what have you. Not bad for a mere dozen lines of code!