I’ve always liked identicons, which WordPress and GitHub have used to great effect. The premise is simple: take a user identifier such as an IP or email address and deterministically convert it into an image based on a simple algorithm. To that I end I started hacking on Identikon - a little Racket program that generates different types of identicons based on rules modules.
Why Racket?
I like Lisps and Schemes and have been experimenting with Clojure for a while as well as noddling around with The Little Schemer and SICP. More importantly though, Racket has a great programming environment with lots of batteries such as the 2htdp/image library for working with graphics. The language was designed as a teaching tool and comes with great documentation.
One of the biggest selling points for me was Matthew Butterick’s Pollen, a Racket type-setting application which he used to publish Practical Typography. He gave a very inspirational talk (video) at RacketCon 2014 which you should check out. Pollen speaks to my art/design background so I decided to take Racket out for a test drive. I’m still very much a noob, and plan to update Identikon as I gain more experience with the language.
Rationale
I wanted Identikon to do a few things:
- Have the ability to generate more than one type of identicon.
- Have the ability to pull in arbitrary rules for generating identicons.
- Work on the command line and within a Racket REPL.
Let’s look at each of those item in a little more depth.
1. Identicons
I had a lot of fun exploring different systems for generating identicons, with varying degrees of success. Some examples:
Squares | Circles | Q*bert | Nineblock |
---|---|---|---|
(identikon 300 300 "racket" "squares")
(identikon 300 300 "racket" "circles")
(identikon 300 300 "racket" "qbert")
(identikon 300 300 "racket" "nineblock")
Each of these images is an identicon for the string “racket” using a different set of rules for generating the image. Racket organizes code into modules which expose a set of public functions to the code that requires it. To make it easy to generate rules for identicons I wanted to keep things simple, so each rules module only has to provide a draw-rules
function with the following signature:
(draw-rules width height user)
In return, draw-rules
has to return an image at the requested size, which is then written to a file or displayed in the REPL depending on how you’re using Identikon. The implementation of draw-rules
is completely the business of the rule module and hidden from the rest of the program.
width
and height
are the pixel dimensions of the image you’re generating, and user
is the data representation of the string given to identikon
. To make unique images we need unique data, so the user
string is first converted to a SHA-1 hash, and then into a list of 20 base-10 numbers, so that “racket” is converted into:
(process-user "racket")
; returns
'(27 180 200 176 189 77 68 156 1 211 209 117 218 72 146 38 144 184 241 76)
This gives the rules module a consistent set of data to work with in generating a unique image. You can use these numbers in a variety of ways, such as generating RGB colors and such. One of the strategies I used in the circles and squares rules is to take the first 15 numbers from the list and break that into 5 lists of 5 mirrored numbers:
; user is '(27 180 200 176 189 77 68 ... 76)
(chunk-mirror2 (drop user 5) 3)
; returns
'((27 180 200 180 27)
(176 189 77 189 176)
(68 156 1 156 68)
(211 209 117 209 211)
(218 72 146 72 218))
Now we have a 5x5 grid of values to fold over, turning pixels on/off depending on the value of the items (ex: even, odd, etc.). Since the list values are “mirrored” the generated image will be symmetrical. I then use the 5 values I dropped from the initial user
list to generate a range of color values to use for the circles or however I see fit. You’re pretty much left to your imagination as to how you use the values generated from the initial string, as long as the output is deterministic.
Other systems are much more complex, like nineblock which packs drawing functions into a vector to be accessed numerically as needed. You can read about nine blocks here.
Of course, once you have something working in a REPL, you can start to have a lot of fun playing around:
(for ([n (range 400 496)])
(print (identikon 80 80 (number->string n) "squares")))
2. Arbitrary rules
Another feature I wanted was the ability to create new rules modules on the fly in discrete files, and allow identikon
to pull them in without any prior registration or configuration. As long as a module followed the draw-rules
API it should be usable. This posed some challenges and required digging into Racket a bit deeper than I planned.
Lisps and Schemes (and therefore Racket) treat code as data, which is a big concept I’m not going to get into deeply here. Suffice it to say, a running Racket program can load in source code from a file, manipulate it and run it within the current environment. Racket manages environments with namespaces - which manage all of the mappings of identifiers to bindings and module instances.
To solve my arbitrary rules module function I needed to create a function that would dynamically load a Racket module file into a new namespace, and then shoehorn that into the existing namespace. Yes, that sounds a little nuts, so lets walk through it (from identikon.rkt):
; Define the draw-rules function as a null value, we will overwrite
; this with the dynamically loaded module's version of draw-rules
(define draw-rules null)
; Create a handle for our new namespace
(define-namespace-anchor a)
; Where we keep the rules modules
(define RULES-DIR "rules")
; Dynamically load in a rules file, attaching it to a new namespace
; that has access to the 2htdp/image module functions (for drawing)
; and make it available to this namespace with (dynamic-require)
(define (load-plug-in file)
(let ([ns (make-base-empty-namespace)]
[filename (build-path (current-directory) RULES-DIR file)])
(namespace-attach-module (namespace-anchor->empty-namespace a)
'2htdp/image
ns)
(parameterize ([current-namespace ns])
(dynamic-require filename 'draw-rules))))
; ... later down inside the identikon function ...
; Overwrite the draw-rules definition with the draw-rules provided
; from the rules file in load-plug-in
(set! draw-rules (load-plug-in rule-file))
Now when we make a call like (identikon 300 300 "racket" "circle")
the program will load the rules/circle.rkt
module and replace the default draw-rules
identifier with the function provided by that module.
This is pretty cool, we can hot-swap the draw-rules
function with a new one whenever we need to. However, this does negatively effect performance, as we have to hit the filesystem for the rules module every time we make an identicon. If I was going to use this to generate identicons at scale I would directly require
just the module I needed and compile it into the program. As a future project I would like to allow a user to compile a version of Identikon with a specific rules module baked in, perhaps via code re-writing during compilation with Racket’s raco tool.
3. Command Line vs. REPL
Identikon will generate images within a Racket REPL simply by calling the identikon
function with valid parameters. If you pass it a file extension it will save the identicon to your filesystem.
You can do the same thing from the command line by running identikon.rkt
with racket and passing in arguments:
$ racket identikon.rkt -s 300 -n racket
This will save out a file called racket.png
containing a 300 pixel default identical. You can see the available arguments with -h
.
Additionally, you can include Identikon into another Racket program:
(require "identikon.rkt")
(define foo (identikon 300 300 "racket"))
We achieve this flexibility very easily in Racket by using submodules. Within identikon.rkt
we define a special main
submodule that is only executed when you run the program from the REPL or command line. This is where we place our command line handling code:
(module+ main
(require racket/cmdline
racket/list)
... handle command line options ... )
This submodule won’t be executed when identikon.rkt
is required into another module. Submodules allows us to interleave code within a program using different environments, which can be pretty powerful. You can use this same technique to interleave your tests within the source code:
(module+ test
(require quickcheck
sugar)
; rhombus-offset calculcation is correct
(define rhombus-offset-outputs-agree
(property ([num arbitrary-natural])
(let* ([onum (rhombus-offset num)]
[diff (- num onum)])
(= num
(* diff 4)))))
(quickcheck rhombus-offset-outputs-agree))
The tests will only be run in a REPL or when you run $ raco test filename
. In this instance I’m using a Racket port of Quickcheck to do some generative testing on one of my functions. You can see a lot more of these at work in the utils.rkt file. I’ve uncovered a lot of loose bugs using quickcheck.
Conclusion
In general I’ve been very happy with Racket for this project. The documentation is good, and there is a mailing list and IRC channel stocked with helpful folks. If you use DuckDuckGo you can quickly search the docs using the !racket
shortcut. Additionally there is a great book, The Realm of Racket, which teaches the basics through game programming. I’m still working through it, but its helped me immensely.
The language itself is big. Unlike its forbear Scheme, it contains lots of sugar and special forms for recursively iterating over different data structures. This helps to reduce the amount of code you need to write.
Racket is still a little niche at the moment. While full-featured it doesn’t have the big community that a language like Ruby or Clojure has, so you have to do a bit more searching for examples and guidance. Also, since I’m coming from Clojure I have a tendency to lean heavily on let
and its recursive cousin let*
in my functions. Racket folks tend to prefer using define
within a function body. This makes my code a little shizophrenic looking at times, and is something I need to work out. Also, while Racket does have immutable data structures, its unclear if they’re persistent or how they’re implemented or compare to Clojure’s persistent data structures. Also, currently there is no RacketScript for the browser (although there is Whalesong).
That said, I’m starting to reach more and more for Racket as my go-to utility language and will definitely keep exploring it in the future.
Comments