4 ms·
Hmm. In many walks of life, popular != best. In fashion, cars, investing, and to some extent cooking, for example, the top performing adherent is doing things d
by martinflack 10y ago
Hmm. In many walks of life, popular != best. In fashion, cars, investing, and to some extent cooking, for example, the top performing adherent is doing things differently than the crowd.
Lisp code can be very readable, but it takes a few years to learn how to write it that way. I spent many years in Perl 5 myself, and I found Common Lisp tricky at first but ultimately very satisfying.
Two jedi mind tricks for the beginner:
1. Don't think of Common Lisp as the language you want. The language you want is one layer above CL, and fits the business domain you operate in. Build that. (Yes, this requires a little investment up front.) Then solve your problems in that "language". Because Lisp macros are powerful fully-transformative engines, you can do almost anything (less breaking a few simple syntax rules... but newbs often underestimate what can be broken/bent).
2. As silly as it sounds, the parentheses coming first does bother folks. Every editor does syntax highlighting now. Do two things: (a) color the parens close enough to your background color that they visually fade and "back off", but are still visible when you need to look at them - your code instantly looks a bit more like python, indentation sticks out; (b) used a structured editing mode that inserts closing parens, keeps parens balanced automatically, and offers neat structured editing hotkeys to copy/cut/paste/delete sexp's (expressions) as units. Both steps are extremely comfort-building and the ubiquitous parens remove a class of bugs.
The greatest compliment to Common Lisp that I've seen over the past 8 years is the constant pilfering of language features to other mainstream languages.
As for readability - like Perl, TIMTOWTDI, and even moreso in Lisp because one can write macros as utilities to greatly simplify the actionable code and have it blow out into lower-level code that does the mechanical dirty work.
As a result of what I've said above, people working in CL do tend to write it slightly differently. You can see some evidence of that if you study different open source libraries, etc. It's not so much a rigid code style as an interest and ethos that binds the community.
I'll leave with a little example of macros at work, via the old and venerable SERIES library, that allows one to specify loops in a semi-declarative style, that if executed literally would cause multiple passes; but which compiles to a single pass over the data sequence.
The input is stock market "bars":
(defvar *spy2006*
'(("29-Dec-06" 142.08 142.54 141.43 141.62 45461200 141.62)
("28-Dec-06" 142.41 142.70 141.99 142.21 37288800 142.21)
("27-Dec-06" 141.87 142.60 141.83 142.51 39727100 142.51)
;; ...
))
This is what you might code, which I humbly submit to you is very readable:
(defpackage iteration-testing-series (:use :cl :series))
(in-package :iteration-testing-series)
(defun combine-bars (bars)
"Summarize a sequence of BARS into one bar."
(series::let ((zbars (scan 'list bars)))
(list (first (first bars))
(second (first bars))
(collect-max (map-fn 'float #'third zbars))
(collect-min (map-fn 'float #'fourth zbars))
(fifth (car (last bars)))
(collect-sum (map-fn 'integer #'sixth zbars)))))
That function is very easy for me to read. Partially expanded, it becomes the following, which is itself further expanded. Note that it was trivial for me to request this expansion, and that multiple macros are composable so that many features can be engage at once. I can write something high-level, but also observe the expansion down to low-level code all the way (SBCL can even trivially show me the resulting assembler code for my CPU). I am freed up to think at a high level - but I don't lose control over the low level.
The following block may be daunting in size but if you know that SETQ is an assignment, and within the TAGBODY there are labels for GO which is like a goto, it should be possible for a programmer of other languages to follow. Not also the type declarations carried over for me automatically, which will allow for speed and debugging optimization, which in CL is a knob that I can turn - something I don't know exists in other languages.
(LET* ((#:OUT-1014 BARS))
(LET (ZBARS
(#:LISTPTR-1012 #:OUT-1014)
(#:ITEMS-1019 0.0)
(#:NUMBER-1017 NIL)
(#:ITEMS-1026 0.0)
(#:NUMBER-1024 NIL)
(#:ITEMS-1032 0)
(#:SUM-1030 0))
(DECLARE (TYPE LIST #:LISTPTR-1012)
(TYPE FLOAT #:ITEMS-1019)
(TYPE FLOAT #:ITEMS-1026)
(TYPE INTEGER #:ITEMS-1032)
(TYPE NUMBER #:SUM-1030))
(TAGBODY
#:LL-1035
(IF (ENDP #:LISTPTR-1012)
(GO SERIES::END))
(SETQ ZBARS (CAR #:LISTPTR-1012))
(SETQ #:LISTPTR-1012 (CDR #:LISTPTR-1012))
(SETQ #:ITEMS-1019 (THIRD ZBARS))
(IF (OR (NULL #:NUMBER-1017) (< #:NUMBER-1017 #:ITEMS-1019))
(SETQ #:NUMBER-1017 #:ITEMS-1019))
(SETQ #:ITEMS-1026 (FOURTH ZBARS))
(IF (OR (NULL #:NUMBER-1024) (> #:NUMBER-1024 #:ITEMS-1026))
(SETQ #:NUMBER-1024 #:ITEMS-1026))
(SETQ #:ITEMS-1032 (SIXTH ZBARS))
(SETQ #:SUM-1030 (+ #:SUM-1030 #:ITEMS-1032))
(GO #:LL-1035)
SERIES::END)
(IF (NULL #:NUMBER-1017)
(SETQ #:NUMBER-1017 NIL))
(IF (NULL #:NUMBER-1024)
(SETQ #:NUMBER-1024 NIL))
(LIST (FIRST (FIRST BARS)) (SECOND (FIRST BARS)) #:NUMBER-1017
#:NUMBER-1024 (FIFTH (CAR (LAST BARS))) #:SUM-1030)))
- flukus 10y ago>This is what you might code, which I humbly submit to you is very readable: It's not, if that's a real life example. There is no context for what the data is, like a variable names.
- martinflack 10y agoValid point. In that example it's using a list to hold each bar because I was focusing on the iteration and the raw data came from a yahoo api utility. You could use a struct or a class which would give you accessor functions, and would be better for a real program. You setup a struct: (defstruct bar date open high low close vol) And this would be a one-time conversion of the data: (mapcar (lambda (b) (apply 'make-bar (mapcan 'list '(:date :open :high :low :close :vol) b))) cl-user::*spy2006*) Then you can access the elements with nicer names: (bar-date bar) (bar-high bar) ...etc... (If you use DEFCLASS you can do a few more things.) That would make the revised COMBINE-BARS look like the following, which has more context, and would yield a BAR struct back. (defun combine-bars (bars) "Summarize a sequence of BARS into one bar." (let ((opening-bar (first bars)) (closing-bar (car (last bars)))) (series::let ((zbars (scan 'list bars))) (make-bar :date (bar-date opening-bar) :open (bar-open opening-bar) :high (collect-max (map-fn 'float #'bar-high zbars)) :low (collect-min (map-fn 'float #'bar-low zbars)) :close (bar-close closing-bar) :vol (collect-sum (map-fn 'integer #'bar-vol zbars)))))) (edit: added revised combine-bars)
- fiddlerwoaroof 10y agoAnother trick is to define the struct like (defstruct (foo :type list) ....) and then you can use the struct accessors on any list of the appropriate size. (Or, if you want constant time element access, specify the type as vector)
- happyslobro 10y agoRegarding the hate for parens: rainbow coloring helps out big time. When each pair of parens has it's own color, it is very easy to see exactly how they are grouped in the more complex statements. For Vim, that would be Rainbowtags. It's actually an improvement for any languages, imo.