Monday, August 10, 2009

Lisp notes





Sun Aug 17 11:17:43 BST 2008

In article <48a7480f$0$26084$db0fefd9@news.zen.co.uk>,
Mark Carter wrote:

> > Hi, Lisp n00b here. I have written a little accounts package in Python,
> > and I was experimenting with converting it to Lisp. I'm using SBCL on OS X.
> >
> > I know almost jack about Forth and APL, but it was occuring to me that
> > it would be a very interesting style to try in Lisp. I think it would
> > make my programs shorter, and would also be useful for making ad-hoc
> > queries.
> >
> > To set the stage, the app that I am writing from the ground up has two
> > classes: account and entry. A ledger is composed of a list of accounts,
> > and accounts have a list of entries in them.
> >
> > I have defined a couple of macros/functions:

Well, you are basically reinventing already existing
Lisp functionality. ;-)

> >
> > (defun foreach-lambda (list func)
> > (loop for el in list do (funcall func el)))

This is called MAP. mapping is a basic concept in Lisp.

(map nil (lambda (item) (do-something item)) list)

There are some other map functions (mapcar, ...).


> >
> > (defmacro foreach (list arg &rest body)
> > "Process each argument in a list"
> > `(foreach-lambda ,list (lambda (,arg) ,@body)))

This is called DOLIST.

(dolist (item list)
(do-something item))

Btw., if you write a macro like above, you should make a small
change:

(defmacro foreach (list arg &body body)
"Process each argument in a list"
`(foreach-lambda ,list (lambda (,arg) ,@body)))

&body informs the Lisp system that body is a sequence
of expressions. It will usually use this information
to format code that uses this macro a little bit better.
You should compare the two. The &rest version
will be indented as if the body were normal arguments.
For the &body version the Lisp system use a different indentation.



> > (defun find-account (code)
> > (find-if (lambda (x) (eq code (account-code x)))
> > *accounts*))

Instead of FIND-IF use FIND - that's shorter.

CL-USER 68 > (find 3 '((a 1) (b 2) (c 3)) :key #'second)
(C 3)

Don't compare numbers with EQ. Use EQL or =.

(find code *accounts* :key #'account-code)

Above will use EQL , but you can tell it to use another:

(find code *accounts* :key #'account-code :test #'account-number-equal)
You might not need it in your case. But this nice
flexibility is there in Common Lisp via various keyword arguments.


> >
> > (defun accounts (func)
> > (foreach *accounts* a (funcall func a)))

(dolist (a *accounts*) (funcall func a))

or shorter

(map nil func *accounts*)

> >
> > (defun account (code func) (funcall func (find-account code)))
> >
> >
> > There's other bits in it too, but this is all I want to demonstrate just
> > now.
> >
> > Well, the nice thing is, if I want to debug or just do an ad-hoc query
> > in the REPL, I can do something like
> > (accounts #'print-balance)

I would rename this function to do-all-accounts or map-accounts.

> > to print the balances of all the accounts. If I am only interested in
> > one account, I can do
> > (account :bank #'print-balance)
> > Or, I can do other things like
> > (account :bank #'describe)

That makes little sense.

(describe (find-account :bank))

is just as good.


> >That's pretty neat, and I'm getting quite enthusiastic about this
> > mysterious beast called Lisp, as it is not an approach I have tried in
> > Python.
> >
> > Although it's quite nice, the way I have done it is not quite right,
> > because if I want descriptions for the entries in an account, the call
> > gets muddles with lambdas. My current definition for showing entries is
> >
> > (defun show-entries (code)
> > "Show the entries for an account code"
> > (foreach (account-entries (find-account code)) e
> > (describe e)))

(map nil #'describe
(account-entries (find-account code)))
> >
> > Now, I'm thinking if I had a stack-based approach, I could define it as
> > something like
> > (-account code) (-entries) (-describe)
> > If you wanted to describe all the entries in all the accounts, then you'd do
> > (-accounts) (-entries) (-describe)
> > where the prefix of '-' denotes a convention that there is a global
> > stack that is being twiddled with. And things like -account would work
> > differently depending on whether the stack was a list of classes or just
> > a class. It also looks really cool; there's no apparent looping
> > construct, and it's very convenient and intuitive for the user.
> >
> > I'm wondering if anyone has adopted a similar or better approach, and
> > what came of it.
> >
> > I'm finding that I can twiddle and fiddle with ideas in Lisp which I
> > probably wouldn't have tried to do in Python. Having said that, I
> > probably would have produced a finished result more quickly.

I think the Lisp-based functional approach is fine. You just
need to use some of the built in stuff. Check the chapters
about lists, vectors, arrays and sequences in the CLHS to get
an overview about the language. Recently here was posted
a pointer to the Common Lisp Quick Reference, which also
gives a nice overview about existing functionality.

-- http://lispm.dyndns.org/



No comments: