Hi. I’m Ryan Moser.

Problem starter. Party solver.

Building Knod, a tiny HTTP server. Part 1: Introduction

While working through Facebook’s excellent React tutorial, I came across a common problem when developing and testing AJAX applications: Having some sort of rudimentary backend is necessary, or at the very least makes things easier. While options for http static servers are many, these only respond to GET requests and hence only get you part of the way there. The ideal solution would also respond to PUT, POST, and DELETE and would work with almost zero configuration.

People build quick prototypes often enough that I assumed a tool must exist for this very purpose. When I could not find such a tool, I built it. Knod, a tiny HTTP server for front-end development, was released on April 12 on RubyGems. The code is available here on GitHub. I limited myself to using the Ruby standard library and learned a lot in doing so. Subsequent posts in this series will detail what I learned about the request/response cycle, writing server tests, and turning a library into a useful command line tool.


Juxtaposition in Ruby

4Clojure recently introduced me to Clojure’s curious juxt function. From the official docs:

Takes a set of functions and returns a function that is the juxtaposition of those functions.

Makes a bit more sense once you see it in practice:

1
2
((juxt + max min) 2 3 5 1 6 4)
; => [21 6 1]

This alone is pretty neat, but it gets better. Jay Fields observed that juxt has other interesting applications:

1
2
((juxt filter remove) even? [1 2 4 3 5 6])
; => [(2 4 6) (1 3 5)]

Implementing juxtapose in Ruby sounds like my idea of a good time. How might we go about this? We immediately run into a problem if we want to mimic the interface of Clojure’s juxt: The first example takes advantage of variadic functions that lack Ruby equivalents. Examining one of these functions, however, gives us a pretty strong hint:

1
2
(+ 1 2 3 4)
; => 10

This looks like a reduce function! Reduce is the most universal of functional programming’s three cornerstone functions (map, reduce and filter), so some sort of reduce-based solution should provide the desired results. In our first example, we pass three functions and get three results back, a pretty strong hint we should map our list of functions over the reduce operation. Our first implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
juxt = ->(*fns) do
  ->(*args) do
    fns.map do |fn|
      args.reduce {|acc, e| fn.to_proc.call acc, e}
    end
  end
end

max = ->(a, b) {a > b ? a : b}
min = ->(a, b) {a < b ? a : b}

juxt.(:+, max, min).(2, 3, 5, 1, 6, 4)
# => [21, 6, 1]

filter = ->(fn, list) {list.select {|e| fn.to_proc.call e}}
remove = ->(fn, list) {list.select {|e| !fn.to_proc.call e}}

juxt.(filter, remove).(:even?, [1, 2, 3, 4, 5, 6])
# => [[2, 4, 6], [1, 3, 5]]

Jackpot! This solution works and we even maintained the variadic interface from Clojure. The downside is that this solution requires us to recreate most of the functionality provided by Enumerable and Array. While I rarely turn down the opportunity to write a stabby lambda, let’s assume we want to avoid this for the sake of argument. We can send symbols to our list of arguments if we are willing to adapt the interface. One way to go about it:

1
2
3
4
5
6
7
juxt2 = ->(*fns) do
  ->(*args) do
    fns.map do |fn, *options|
      options.empty? ? args.send(fn) : args.send(fn, &:"#{options.first}")
    end
  end
end

This version of juxt brings a second meaningful improvement on top of being able to use Enumerable: Now we can juxtapose methods that require a block alongside those where a block is optional:

1
2
juxt2.([:select, :even?], :max, [:reduce, :*]).(1, 2, 3, 4, 5, 6)
# => [[2, 4, 6], 6, 720]

I find this diversion so interesting because I only considered doing things like this in Ruby after I discovered Clojure. There is much to be learned about Ruby by exploring functional programming langugages.


Clojure Metaprogramming with Sequences

Generating methods based on the content of arrays, hashes, and other Enumerable things is a powerful metaprogramming technique in Ruby. To keep things relatively simple, let’s use an example problem from Katrina Owen’s fantastic site Exercism:

Write a program that, given an age in seconds, calculates how old someone is in terms of a given planet’s solar years.

We know the length of an Earth year in seconds and the length of every other planet’s orbital period in terms of earth years. Here is an implementation in Ruby:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SpaceAge
  attr_reader :seconds

  SECONDS_PER_EARTH_YEAR = 31557600.0

  ORBITAL_PERIODS = {mercury:  0.2408467,
                     venus:    0.61519726,
                     mars:     1.8808158,
                     jupiter:  11.862615,
                     saturn:   29.447498,
                     uranus:   84.016846,
                     neptune:  164.79132}

  def initialize(seconds)
    @seconds = seconds
  end

  def on_earth
    seconds / SECONDS_PER_EARTH_YEAR
  end

  ORBIT_PERIODS.each do |planet, earth_years_per_orbit|
    define_method "on_#{planet}".to_sym do
      on_earth / earth_years_per_orbit
    end
  end
end

We use metaprogramming in lines 22 through 26 to generate methods for ages on every planet other than Earth based on our ORBITAL_PERIODS hash. This will make it super easy to change this class when we are done with Earth and want to define everything in terms of Martian years.

Writing the Clojure equivalent of this implementation proved a bit more difficult than expected. Let’s set up the Clojure equivalent and work through the metaprogramming piece:

1
2
3
4
5
6
7
8
9
10
11
12
13
(ns space-age)

(defn on-earth [seconds]
  (/ seconds 31557600.0))

(def orbital-periods
  {:mercury 0.2408467
   :venus   0.61519726
   :mars    1.8808158
   :jupiter 11.862615
   :saturn  29.447498
   :uranus  84.016846
   :neptune 164.79132})

How might we generate functions from our orbinal-periods hashmap? A list comprehension with for feels pretty close to the mark, but this cannot work because it yields a lazy sequence. We need to execute the contents of this sequence to get the functions we are creating into our namespace. Clojure’s’ doseq macro is purpose built for this use case. Now we have the start of our solution:

1
2
3
(doseq [[planet period] orbital-periods]
  ;somehow make functions
  )

I had a bit of trouble wrapping my mind around this part of the problem because I taught myself Clojure with resources placing a heavy emphasis on lazy evaluation and side-effect free functions. This case runs totally counter to that, executing a sequnce specifically for its side effects, which happen to be producing pure functions.

Now that we know something will execute, we must determine what to execute to generate a function from a key in the orbital-periods hashmap. My first instict was to try something like this:

1
(defn (string "on-" planet) [seconds] ;do stuff)

This fails because the first argument to def or defn must be a symbol at readtime. intern solves this problem by finding or creating a var by the supplied symbol at runtime. From there, it is as easy as building the function we want bound to that var:

1
2
3
4
(doseq [[planet period] orbital-periods]
  (let [fn-name (symbol (str "on-" (name planet)))]
    (intern *ns* fn-name
      (fn [seconds] (/ (on-earth seconds) period)))))

Better Tests Through Metaprogramming

Duplication causes all of the same maintainability issues in test suites it does in production code, but I often see the DRY principle violated in the name of comprehensive test coverage and fidelity.

Let’s say we have a Rectangle class that normalizes a range of inputs and we want to test all of them. How can we accomplish this without:

  • Writing an individual test for each input (duplication)?
  • Writing one test with multiple assertions (lost fidelity)?

Consider this approach:

1
2
3
4
5
6
7
8
9
subject {Rectangle}
formats = {comma_delimited_string: '100, 100, 500, 500',
           array_with_strings: ["100", "100", "500", "500"],
           array_with_ints: [100, 100, 500, 500]}
formats.each do |(format_name, data)|
  it "accepts and normalizes data passed in as a #{format_name}" do
    subject.create(data).coordinates.should == formats[:array_with_ints
  end
end

String interpolation in the test description provides nearly the fidelity of breaking out assertions into individual tests. Neat!


Destructuring in Clojure and Ruby

As a long time Rubyist who picked up Clojure earlier this year, I have noticed the following pattern repeats itself:

  1. Encounter some new concept in Clojure
  2. Become confused by said concept and spin my wheels for a bit
  3. Realize I was using this concept in Ruby without knowing its name and full potential
  4. Simultaneously level up Ruby and Clojure skills

Destructuring is the perfect example of one such concept. Per the offical Clojure documentation:

Clojure supports abstract structural binding, often called destructuring, in let binding lists, fn parameter lists, and any macro that expands into a let or fn.

It was not immediately obvious to me based on this definition, but this is actually quite common in Ruby. Consider the following:

1
2
3
4
5
some_hash = {a:1,b:2,c:1}

some_hash.each do |(k,v)|
  # do stuff
end

Using (k, v) effectively tells Ruby:

  1. This element has two parts
  2. Assign the first part of that element to “k”
  3. Assign the second part of that element to “v”

Destructuring within list comprehenstions in Clojure looks remarkably similar:

1
2
3
(defn use-a-hashmap [some-hashmap]
  (for [[k v] some-hashmap]
   ; do amazing functional things

We can also destructure method arguments in Ruby. Consider a method that takes a three element array as an argument. Instead of this:

1
2
3
4
def do_thing_with_point(point)
  x, y, z = point
  # do stuff
end

We can use destructuring to skip a step:

1
2
3
def do_thing_with_point((x,y,z))
  # do stuff
end

As with many things in Ruby, the better question than “Can it be done this way?” is “Is it wise?”. Think this is a win because the second example gives (slightly!) more immediate insight into the argument this method accepts.