
-----------------------------------
wtd
Thu Feb 02, 2006 6:31 pm

[Lisp-tut] Taking a format string apart
-----------------------------------
Taking a format string apart

First let's take a look at the format string in action.

[1]> (format nil 
             "~{~#[~;~A~;~A and ~A~:;~@{~A~#[~;, and ~:;, ~]~}~]~}" 
             '(1 2 3 4))
"1, 2, 3, and 4"
[2]> (format nil
             "~{~#[~;~A~;~A and ~A~:;~@{~A~#[~;, and ~:;, ~]~}~]~}"
             '(1 2))
"1 and 2"

Terror is understandable

That's one frighteningly complex string, isn't it?

The key, though is quite simple.  You merely have to break it down into smaller pieces that are easier to understand.

Basics

The tilde (~) introduces a format directive.

The simplest of these is ~A, which can represent anything.

The ~{ and ~} directives deal with looping and ~So... what's going on in that string?

The outer directives are ~{ and ~}.  This takes a list and iterates over it.  The enclosed directives are evaluated for that list.

Next we have ~#The final clause

This one is decidely less simple than the previous three, and yet, not beyond our understanding.

~@{~A~#[~;, and ~:;, ~]~}

The outer ~@{ and ~} directives are again an iteration construct.  The @ symbol added in means that it will iterate over anything left.  In this case, that means it will iterate over the list the outer iteration directives grabbed.  It's iterating over 1, 2, 3, and 4.

Within these directives we first output a single item.  Then we have a conditional which decides what should follow that item.

If zero items are left, then output nothing.  

If only one item is left, output ", and".

And if there are two or more items left, the separator is a comma and a space.

Why?  Why?  WHY?!

Why should we subject ourselves to such unspeakable horrors?

Surely there must be a more readable way to express this logic.

class Array
   def join_as_english_list
      case length
         when 0
            ""
         when 1
            first.to_s
         when 2
            "#{first} and #{last}"
         else
            "#{self[0...-1].join(", ")}, and #{last}"
      end
   end
end

Now, in all honesty, that's not so bad, but then again, Ruby's pretty expressive.  

It's still a fair amount of code to delve through, and it hides some of its complexities in library methods that must be understood to fully appreciate what this new method is doing.

Is this really more readable then, if a programmer can recognize the individual components of the format string?
