Using Toccata

Every tool is designed with a certain use in mind and using the tool in the way it was designed is often much easier. So starting with this post, I want describe how I intended Toccata to be used; The Toccata Way.

However, programming languages are incredibly malleable, so what I describe as The Toccata Way could very well change as people use it and discover better ways of doing things. Nothing I write here is cast in stone and I would love to be shown better ways of creating software using Toccata.

The programming triple

All programming boils down to telling the computer to do something in very small steps and there are 3 fundemental constructions used to build a whole program out of these small steps.

  • Executing steps in sequence one after the other.
  • Choosing one of a number of sequences of steps to execute.
  • Executing a sequence of steps repeatedly with different inputs

Together, I consider these the "control-flow triple" and it appears everywhere I look at all levels of abstraction from assembly language to the highest level languages. Even declarative logic programming languages like miniKanren (or Prolog), have analogous constructions.

In Toccata, and most other languages, sequencing steps is so trivial that it's almost invisible

  (fn [x y]
    (expr1 x)
    (expr2 y (expr3))

expr1 is done first followed by expr3 and finally expr1 which produces the final result.

I'll address repeatedly executing a sequense of steps in the next post. So now, let's look at choosing.

A Time for Choosing

Let's revisit the hello-world program:

  (main [args]
    (println "Howdy, Folks"))

"Folks" is a little generic, so let's personalize it a little by letting a name be passed in on the command line.

The args parameter to the main function is a list of strings that are the command line arguments to the program. The first string in the list is the name of program that was executed which we'll mostly ignore. So we need to get the second item from the list. The second function from the core library does exactly that. Except it returns a Maybe value that we need to extract the actual name from. But there might not have been a name passed in on the command line. So we need to account for that.

  (main [args]
        (println 'args args)
        (println "Howdy," (either (second args)
                                  "Folks")))

I encourage you to run this for yourself.

Short-circuit

Let's say we want to personalize it further by having a table of names mapped to a corresponding greeting.

  (def greetings {"Jim" "Howdy"
                  "Frank" "Hello"
                  "Maria" "Bonjour"
                  "Tina" "Beautiful"
                  "Human" "Greetings"})

So we need to get the second arg as above, and then use that name to get the salutation. But several things could go wrong. The name may not be in our map, or there may not be a name given on the command line at all. We'll use the flat-map function, which is defined for Maybe values, to chain some calls to functions together sequentially. But if any of them return nothing, the whole expression short-circuits and returns nothing.

  (main [args]
    (println (either (or (flat-map (second args)
                                   (fn [name]
                                     (map (get greetings name)
                                          (fn [salutation]
                                            (str salutation ", " name)))))
                         (map (second args)
                              (fn [name]
                                (str "Howdy, " name))))
                     "Howdy, Folks")))

In this case, the map expressions both apply a function to a Maybe value. The anonymous fns only get called if the Maybe values are not nothing. Hopefully that construction is obvious given the explanation in the Containers post.

Let's clean this up a little. We can use a for expression to refactor the flat-map expression.

  (main [args]
    (println (either (or (for [name (second args)
                               salutation (get greetings name)]
                           (str salutation ", " name))
                         (map (second args)
                              (fn [name]
                                (str "Howdy, " name))))
                     "Howdy, Folks")))

Having two calls to second is a little ugly, so let's pull that out

  (main [args]
    (let [maybe-name (second args)]
      (println (either (or (for [name maybe-name
                                 salutation (get greetings name)]
                              (str salutation ", " name))
                           (map maybe-name
                               (fn [name]
                                 (str "Howdy, " name))))
                          "Howdy, Folks"))))

I'm sure there are other ways to write this little ditty that improve on this.