Skip to content
marick edited this page Nov 4, 2011 · 7 revisions

Sometimes it's convenient to illustrate a fact with a table of values. For example, Conway's Life has rules for when cells are born, stay alive, or die. They look nice expressed as a table:

     (tabular
      (fact "The rules of Conway's life"
        (alive? ?cell-status ?neighbor-count) => ?expected)
      ?cell-status   ?neighbor-count   ?expected
      :alive         1                 FALSEY        ; underpopulation
      :alive         2                 truthy       
      :alive         3                 truthy
      :alive         4                 FALSEY        ; overpopulation
     
      ;; A newborn cell has three parents
      :dead          2                 FALSEY
      :dead          3                 truthy
      :dead          4                 FALSEY)

Note that you can capitalize falsey. FALSEY and TRUTHY are synonyms for the lowercase form. I find that using the capitalized form for one makes a tabular fact easier to read.

In case of a failure, the line number points to the check (the arrow form), as usual. But it's augmented by a description of which set of values caused the failure. For example, if alive? always returns false, the fact above will yield three failures:

     FAIL at (t_sweet.clj:237)
     With table substitutions: {?cell-status :alive, ?neighbor-count 2, ?expected truthy}
     Actual result did not agree with the checking function.
             Actual result: false
         Checking function: truthy
     FAIL at (t_sweet.clj:237)
     With table substitutions: {?cell-status :alive, ?neighbor-count 3, ?expected truthy}
     Actual result did not agree with the checking function.
             Actual result: false
         Checking function: truthy
     FAIL at (t_sweet.clj:237)
     With table substitutions: {?cell-status :dead, ?neighbor-count 3, ?expected truthy}
     Actual result did not agree with the checking function.
             Actual result: false
         Checking function: truthy

Details

Substitutions work with more than just plain values. For example, instead of using truthy and falsey, you can substitute the checking arrow, or you can substitute a calculation for a value:

     (tabular
      (fact "only two numbers have the same sum and square"
        (* ?n ?n) ?arrow (+ ?n ?n))
      ?n        ?arrow
      0         =>      
      2         =>
      ;; Failure cases
      1         =not=>
      (* 10 10) =not=>   ; note calculation
      ;; and so on
      )

Tabular facts behave exactly like regular facts. They can be turned into future facts:

     (tabular
      (future-fact (inc ?int) => ?int)
      ?int
      1)

They obey background prerequisites, setup, teardown, and state, and so on.

You can have many checks within the fact. (That is, you're not limited to a single arrow.)

tabular can take a doc string:

     (tabular "increment works"
      (fact (inc ?int) => ?int)
      ?int
      1)

This is convenient when using M-x midje-hide-facts in Midje mode.

How it works

tabular is a macro whose arguments should look like this:
(tabular target-form & headers+values)

The header consists of 0 or more ?-variables. The remaining values must be a whole-number multiple of the number of ?-variables; you can consider them divided into rows.

tabular expands number-of-rows copies of its target-form, each with the ?-variables substituted with their corresponding row values. A ?variable can appear more than once in the target-form. It's not an error if a ?-variable doesn't appear in the target-form.

Clone this wiki locally