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:
(defun foreach-lambda (list func)
(loop for el in list do (funcall func el)))
(defmacro foreach (list arg &rest body)
"Process each argument in a list"
`(foreach-lambda ,list (lambda (,arg) ,@body)))
(defun find-account (code)
(find-if (lambda (x) (eq code (account-code x)))
*accounts*))
(defun accounts (func)
(foreach *accounts* a (funcall func a)))
(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)
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'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)))
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.
In article <·························@news.zen.co.uk>,
Mark Carter <··@privacy.net> 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/
Rainer Joswig wrote:
> Well, you are basically reinventing already existing
> Lisp functionality. ;-)
Thanks for your help - very useful. My code is looking rather neater now!
I'm not what you'd call an OO fanatic, but I find that using classes
seems to help. I played with Cells, but it wouldn't give me any loving.
On Aug 16, 11:35 pm, Mark Carter <····@privacy.net> 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'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.
Faster results can often be achieved by using the appropriate building
blocks. You might be able to leverage John Wiegley's CAMBL for
monetary calculations:
http://github.com/jwiegley/ledger/tree/cambl-20080203
He also used it for a ledger application (a port of his C++ ledger):
http://github.com/jwiegley/ledger/tree/cl-ledger-20080203
Michael Weber wrote:
> On Aug 16, 11:35 pm, Mark Carter <····@privacy.net> 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'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.
>
> Faster results can often be achieved by using the appropriate building
> blocks. You might be able to leverage John Wiegley's CAMBL for
> monetary calculations:
> http://github.com/jwiegley/ledger/tree/cambl-20080203
>
> He also used it for a ledger application (a port of his C++ ledger):
> http://github.com/jwiegley/ledger/tree/cl-ledger-20080203
Thanks. Actually I was aware of cl-ledger. I used to use Microsoft Money
(I know, boo hiss), which served my needs quite well, actually. I bought
a Mac, so I no longer use Windows. I used Linux for quite some time on
my Mac, but then I got OS X Leopard, and figured that Linux is too much
like hard work.
It's always a bit of a dilemma - do I use what's already available, or
do I roll my own? The first thing I tried was GnuCash. The problem is,
it didn't produce the reports I wanted it to. It can be customised, but
it's not easy. I saw one report that was about 800 lines of Guile.
I came to the conclusion that it'd be easier to hand-roll a solution in
Python - which is what I did. The whole shebang came to about 1500 lines
- although the count tend to go up and down depending on what features I
decide to add or remove.
Becoming dissatisfied with what I had done (I was originally thinking of
plopping a UI on top of it), I cast around for other solutions. I came
across SQLedger, but the whole database deal seemed over the top for
what I wanted. cl-ledger seemed interesting enough, but probably quite
an investment in learning it to get it to do the particular things I had
in mind.
I had thought of rewriting my app in Ruby, but after a series of ums and
arrs, I decide to have another go at Lisp. So far, the application is at
132 lines, and already does a fair bit. It doesn't yet do stuff like
download share prices, keep them in a database, and other fancies; but
it's surprising what you can accomplish with very little. I thoroughly
expect it to be smaller than my Python program - although I put that
down to the inelegance of my solution rather than problems with Python.
Mark Carter <··@privacy.net> writes:
> 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.
http://groups.google.com/group/comp.lang.lisp/msg/6cbae7d520fc8c05
Now, the question is what problem would require the FORTH paradigm as
a solution?
--
__Pascal Bourguignon__ http://www.informatimago.com/
Until real software engineering is developed, the next best practice
is to develop with a dynamic system that has extreme late binding in
all aspects. The first system to really do this in an important way
is Lisp. -- Alan Kay