Let me guess: you're coming to Lisp from C, Perl or Java?
There are two giveaways: the indentation of the closing parentheses, and the way you're using variables.
While you can
write C in Lisp, you're setting yourself up to fight the language instead of getting it to work with you. This is where we run into probably my favourite analogy describing functional programming: it's like roller-skating. It doesn't work all that well without skates, and they're awkward to walk in (what you're doing with the code above). Getting the hang of it can be hard, and it's easy to get frustrated and go back to walking. When you start skating with those things actually attached to your feet, though, suddenly it all makes sense and you understand what the fuss was about.
Something that it's important to understand is the "function" part of "functional programming" - while C is oriented around creating structures and then poking at them from the outside, Lisp allows you to pass the output of one function directly to another without having to explicitly name it. If there were a single thing that constituted the roller-skates of Lisp, this would be it. This difference goes right to the heart of how C and Lisp approach things, which is a discussion for another post.
The natural question following this, of course, is "OK, smartarse, what is
the idiomatic approach?"
That would be to write a function which takes a list as its argument, and which returns a new list composed of lists containing pairs of the elements in the original list. How you handle odd-numbered lists is up to you: either respond with (error "List must have an even number of arguments!")
, wrap it somehow as the last element (more on this in a minute) or silently drop it.
In case you're still getting the hang of lists, they're created via the (cons)
function. The catch is that it's only a list in Lisp terms if the last element is NIL (a.k.a. the empty list) - that is, (cons "a" nil)
is a list, as is (cons "a" "b" nil)
, while (cons "a" "b")
is "merely" a dotted pair. The practical differences that are most likely to be of interest here is what you get when you call (cdr)
on the result - it'll give you the last element of a dotted pair, but what you get back from the other constructions is... probably something you want to explore yourself, with a useful reference at hand.
Where I'm going with all this is that you do have to decide what form the "pairs" should take in the output list, which should be determined by what you plan to do with them next. Sometimes you'll want (list "a" "b")
; other times you'll want (cons "a" "b")
I really hope that made sense, but do feel free to point out where I just sound like I'm babbling.
What I'd probably do when writing something like this is set up the scenario, then construct the machinery that makes it work. First, the scene:
- Code: Select all
(defvar *foo* (list "a" "b" "c" "d" "e" "f"))
It's useful to know that (list "a" "b" "c" "d" "e" "f")
is very different to '("a" "b" "c" "d" "e" "f")
- the former generates a brand new list each time you call it, while the latter refers to an immutable structure in memory. When doing something like this, you almost certainly want to use (list)
unless you have a specific reason not to.
It's also handy to know that variables are declared/initialised via (defvar)
is for updating the value of an existing variable; it's bad form to use it to initialise one. The reason for having two ways of declaring a variable is that (defvar)
will only set the value of the variable if it doesn't exist; (defparameter)
will set it every time. This relates to the interactive way in which Lisp code is normally written and tested: if you reload a source-file into a running image, you may or may not want the current state of a variable reset to its beginning value.
The next part is writing
- Code: Select all
, which starts like this:
- Code: Select all
(defun list-into-pairs (lst)
;iterate over the input list, collecting pairs of elements
Why do it like this? One thing that Lisp has really taught me is not just to solve the exact problem at hand, but instead to recognise the entire class of problem it represents, and to solve that whole class at once. My code typically winds up as a library plus an application that happens to call functions from it. Conveniently, both approaches seem to involve about the same amount of work... at least, they do until you need to add things like error-handling. But then when I need to solve that same problem again in a slightly different form, I already have the solution at hand. Also, if all you needed was that exact list of strings grouped in pairs, you'd just create that group of pairs in the first place, wouldn't you?
Because this looks remarkably like homework, I won't give you the actual code for turning the input list into a group of pairs, since that would seriously reduce the amount you'd learn from the exercise, and that would defeat the entire point of it. However, I've hopefully given you a bit of help on the way. Incidentally, I'd be inclined to use (loop)
with its collecting
keyword to implement the approach you've used, but there's always more than one approach.