1. 40
  1.  

  2. 14

    Another benefit (which doesn’t seem to be in the post) – when you’re building a decision table and realize there’s cases where you don’t know how they should be handled, or (oh no!) you’ve been handling it two different ways, depending on the order different code paths check the variables.

    1. 7

      This is how I have used them in the past. Sometimes I’ve even directly encoded the table as a lookup table, mapping the actions to functions.

    2. 10

      Using match statements in Rust makes encoding decisions tables very clean, which is something I’ve come to like a lot when writing “complex” logic. Encoding the FizzBuzz example in Rust looks like the following code.

      let output = match (n % 3, n % 5) {
                       (0, 0) => "FizzBuzz".to_owned(),
                       (0, _) => "Fizz".to_owned(),
                       (_, 0) => "Buzz".to_owned(),
                       (_, _) => n.to_string(),
                   };
      
      1. 5

        I use this pattern a lot in Haskell with case.

        In languages without match/case/cond/etc. we can use a datastructure to enumerate all the possibilities, e.g. a Python dictionary:

        def go(x, y):
          return {(0, 0): "foo",
                  (0, 1): "bar",
                  (1, 0): "baz",
                  (1, 1): "quux"}[(x, y)]
        

        If we don’t want to enumerate them all (e.g. like the _ wildcards in your fizzbuzz example) then we can calculate booleans for the keys, and lookup whichever is True:

        def go(n):
          n3 = n % 3 == 0
          n5 = n % 5 == 0
          return {(    n3 and     n5): "fizzbuzz",
                  (    n3 and not n5): "fizz",
                  (not n3 and     n5): "buzz",
                  (not n3 and not n5): str(n)}[True]
        

        I’m not sure if this counts as “simple” or “too clever”, but I’ve found it useful on occasion. The tricky part is that we cannot allow more than one of the booleans to be True at once, since they’ll collide; in this case we need to add and not ... conditions to the fizz and buzz entries to prevent them colliding with the fizzbuzz entry.

      2. 4

        Reminded of this paper about Schematic Tables: http://www.subtext-lang.org/OOPSLA07.pdf

        Screencast: https://vimeo.com/140738254

        From http://www.subtext-lang.org/

        1. 3

          John Nagle (“Animats”) suggested they be used for smart contracts since they’re easy for both humans and machines to understand. I liked that idea.

          1. 3

            I follow Hillel on Twitter and he posts a ton of great content about formal verification, correctness, and programming in general. This particular post is really terrific.

            1. 3

              Decision tables are useful on a whiteboard, but manually checking a decision table for completeness can be surprisingly tricky and error prone. They really benefit from being written down in a (programming) language. Then they are quite easy to check for completeness and also pretty easy to minimize (figure out “don’t cares”, columns subsumes by other columns, etc. As an extension, you can assign (multiple) actions to be taken for a certain valuation of the conditions.

              The tricky things are supporting a nice way to specify the tables in your language and a sufficiently powerful way of specifying the conditions to evaluate and actions to perform.

              1. 2

                Great post - I admit to being a bit curious to see those rspec macros too, even if it’s not a general approach.

                1. 5

                  I don’t know about macros; but, I often generate tests from data in rspec. I too am curious what the macros bring to the table.

                  I found this rspec truth table DSL too. It has a nice syntax…

                  1. 2

                    I really wish I could share them, but it was at an old job and I don’t have the source code :(

                    1. 1

                      I don’t know about Rust. The matching in F# allows coding up sophisticated match situations. The compiler will tell you if you have left out a case. My favorite example is balancing a Red/Black Tree. This would be many lines of hard to understand imperative code:

                      type a t =

                      | Node of color * a * a t * a t 
                      
                      | Leaf
                      

                      let balance = function

                      | Black, z, Node (Red, y, Node (Red, x, a, b), c), d
                      
                      | Black, z, Node (Red, x, a, Node (Red, y, b, c)), d
                      
                      | Black, x, a, Node (Red, z, Node (Red, y, b, c), d)
                      
                      | Black, x, a, Node (Red, y, b, Node (Red, z, c, d)) ->
                      
                      	Node (Red, y, Node (Black, x, a, b), Node (Black, z, c, d))
                      | x -> 
                      	Node