Lisp's mysterious tuple problem
codeproject.com
codeproject.com
You discovered structs, but you should not have stopped there. Structs can be configured. You can declare boa constructors (yes, really, By Order of Arguments). You can configure the names of the accessors. You might even create a little reader macro for creating them. You might want to write a little with-3dvecs macro for quick destructuring. When structs do not have everything you need, maybe go to CLOS classes.
In Python, “there is only one way to do it”. In Perl, “there is more than one way to do it”. In Lisp, there are thousands of ways to do it. If you haven't found a good one for your problem yet, keep looking. I promise, there is at least one.
By the way, you also have no idea of Clojure, but I have only been using it for a few years, so I feel not confident to give meaningful hints.
It's way more common to use key/value maps, precisely because Clojure has a simple, universal reader macro for representing it. Likewise, his Clojure example at the end:
(def point ['vec3d 3 4 -3])
(match [point] ['vec3d x y z] (printf "(%s,%s,%s)" x y z))
is absurd. I can't imagine anyone familiar with Clojure choosing that over a simple map or record. E.g.:
{:x 3 :y 4 :z -3}
This article is shit, and it amazes me that a website called "the code project" could greenlight it.
There is so much wrong with the article, but it can be summarized on: People who don't know lisp shouldn't attempt to criticize it.
Exhibit A of ignorance:
"Of course Lisp has alternative ways to store tuples. It has structs; it has objects; it has multiple types of structs and objects. However, most Lisp programmers and programs don't use them very much, and later we'll talk about why."
Which is patently untrue; in fact there is a legion of lispers that were attracted to it due to its object system.
Exhibit B:
Author makes a really poor attempt to work with vectors, by just defining a simple struct, and then complains about the verbose syntax.
If he was a person who really used lisp for writing an actual working, useful program, he would already know that if he needs brief syntax for his own custom vector, has many options:
1. use array notation: #(1 2 3) 2. write a macro 3. write a macro to define his own custom vector delimiters. 4. create an object, add necesary methods. 5. a combination of anything above.
There is so much wrong with the article, the author seems to even doesn't know about association lists and plists; etc.
By that time, C had long been a popular language, in spite of demanding that, just like in Pascal, local variables have to be defined at the opening of a new block scope, after which only statements follow.
This is actually a good idea; I avoid mixed declarations and statements in C programming.
Also, mixed declarations and statements are against the Linux kernel coding guidelines and are diagnosed. Yet countless people hack on the Linux kernel for fun and profit.
When I really need some tightly scoped local variable in C, I introduce a little scope with a new braced compound statement, just like `(let` in Lisp. This has the advantage that you determine where the scope ends, not only where it begins. You know: from line 27 this 75 line function, down to line 53, and that's it, not all the way down to line 75.
The mainstream dialect of lisp known as Scheme has a define construct which lets you create new scopes with local variables at the same level of nesting. (Scheme compilers transliterate these defines-s to nested let for you). Yet Scheme is not taking over the world. This aspect rarely even comes up as a topic.
You can have more than one value in a let and it typically does not cause problems. If you do find it causes problems, it is trivial to change the language and invent your own, expression for defining local variables.
Its fine to have your reasons for hating lisp, but if this is the only thing that puts you off, I recommend giving it another try.
There is this one: simple statistics. People have created an astonishing number of programming languages, of which only a vanishing minority are popular. If you pick a language randomly out of all of the ones that have been ever created, you will with a very high probability close to 1 land on something that is not used at all.
Given the age of the first versions of Lisp, it is astonishing that the descendants are still here and that there is a lot of resemblance.
(defun foo (a &aux b (c 1))
(list a b c))
is (defun foo (a)
(let (b (c 1))
(list a b c)))If I had to use a Lisp-like language, I'd choose Clojure so I can interoperate with the JVM. (I use Scala already, so I could interoperate with that, too.)
Suppose that we have used (cddr x) to test whether the list x continues after the second item. It is then unnatural to have (third x) to retrieve the third item! It is like "faulty parallelism" in your English essay. :)
If the condition (and (cdr x) (consp (cddr x))) holds, then the proper way to get that item is (caddr x). This is easy to verify: If cddr is a cons, then its car is given by tacking on an "a": caddr.
In any case, car, cdr and the rest are an absolute must in a Lisp dialect. They are instantly understandable; there is no good reason to break with this important convention. A Lisp dialect will not achieve anything by breaking cultural compatibility with other dialects in this regard; it will just be shunned by Lisp people yet still disused by everyone else. Although CAR and CDR come from IBM 704 machine language, which means nothing to pretty much anyone over fifty years later, the people who allowed these names to infect the higher level language weren't idiots. They discovered that the names just work and so used them. Borrowing some mnemonic that works from the IBM 704 is no worse than borrowing some equally arbitrary greek letter like lambda from a branch of mathematics. If the mathematics of anonymous functions had been called "Tau Calculus" we would be writing (tau (x y) + x y) instead of lambda; it's just a historic accident. All names are historic accidents: one person calls it water, another one aqua, a third one mizu.
I also love the star convention and have used it in many places. for instance mapcar* is a lazy mapcar: it can take infinite lists as its arguments and returns the resulting lazy list immediately. Leslie Lamport picked up on this star notation in LaTeX; that's where I first encountered it. When I got into Lisp years later it was like, hello, is that where Lamport got it from?
I don't like verbosity, so I made my dialect slick and ergonomic. It can "code-golf" side by side with the so-called "modern" scripting languages, while remaining clear.
As a minor point, the values in an alist are just the cdrs of each pair, so your point alist would be written '((x . 1) (y . 2) (z . 3)); yours has each value being a one-element list. But anyway, if that level of verbosity doesn't bother you, you could just use a struct like the article mentions and write:
(defstruct point x y z)
(defvar *point* (make-point :x 1 :y 2 :z 3))
(format t "~&x is ~a" (point-x *point*))
The author claims that this is too verbose and therefore no one uses structs or classes, which is not at all true in my experience, and it doesn't look that verbose to me either. If the point- prefix really bothers you, you can say (defstruct (point (:conc-name)) x y z) and then the accessors will be defined without it. CL-USER 202 > (gp:make-graphics-state :thickness 2 :dashed t)
#S(GRAPHICS-PORTS:GRAPHICS-STATE :TRANSFORM (1 0 0 1 0 0) ...)Oh, wow, a graphics state for drawing is not a list, but a structure object.
CL-USER 204 > (make-random-state)
#S(RANDOM-STATE :J 44 :K 20 :SEED ...)
Oh, wow, a random state is not a list, but a structure object. CL-USER 205 > (pathname "/foo/bar/baz.lisp")
#P"/foo/bar/baz.lisp"
CL-USER 206 > (describe *)
#P"/foo/bar/baz.lisp" is a PATHNAME
HOST NIL
DEVICE NIL
DIRECTORY (:ABSOLUTE "foo" "bar")
NAME "baz"
TYPE "lisp"
VERSION NIL
Oh, wow, a pathname is not a list or string, but a pathname object.The Lisp I'm using has literally thousands of structure classes and CLOS classes defined. A Lisp Machine of the mid 80s had around 6000 classes defined.
CL-USER 158 > (defclass vec3d ()
((x :initarg :x) (y :initarg :y) (z :initarg :z)))
#<STANDARD-CLASS VEC3D 40201F4F8B>
CL-USER 159 > (with-slots (x y z) (make-instance 'vec3d :x 3 :y 4 :z -3)
(format t "(~S,~S,~S)" x y z))
(3,4,-3)
The only thing to understand is that Lisp code is usually not optimized for shortest code on the token level.Though more compact versions of the DEFCLASS macro are being used sometimes. If an application needs a shorter notation, the programmer can always use macros and read-macros to introduce that.
The advantage of CLOS instances and structures over hashtables/vectors/lists is that they carry their class and that they use features like inheritance and method dispatch. The usual OO stuff.
Structures can have by-order-arguments and already print themselves readable:
CL-USER 162 > (defstruct (svec3d (:constructor svec3d (x y z))) x y z)
SVEC3D
CL-USER 163 > (svec3d 1 2 3)
#S(SVEC3D :X 1 :Y 2 :Z 3)
A structure can also have accessor functions without the structure name prefix: CL-USER 168 > (defstruct (svec3d
(:constructor svec3d (x y z))
(:conc-name))
x y z)
SVEC3D
CL-USER 169 > (let ((p (svec3d 1 2 3)))
(format t "(~S,~S,~S)" (x p) (y p) (z p)))
(1,2,3)
NIL
One can also provide multiple constructors and embed literal objects in code: CL-USER 187 > (defstruct (svec3d (:constructor svec3d (x y z))
(:constructor make-svec3d)
(:conc-name)) x y z)
SVEC3D
CL-USER 188 > #S(SVEC3D :X 1 :Y 2 :Z 3)
#S(SVEC3D :X 1 :Y 2 :Z 3)
CL-USER 189 > (let ((p #S(SVEC3D :X 1 :Y 2 :Z 3)))
(format t "(~S,~S,~S)" (x p) (y p) (z p)))
(1,2,3)
NIL
Some implemetations also support the use of WITH-SLOTS with structures: CL-USER 190 > (with-slots (x y z) #S(SVEC3D :X 1 :Y 2 :Z 3)
(format t "(~S,~S,~S)" x y z))
(1,2,3)
NIL
The tradition of Lisp usually is to give the programmer the choice of representation: adhoc lists/vectors/multiple values or structures (efficient records) or CLOS classes (flexible objects) or any kind of user defined stuff...For any larger code and CLOS classes are usually the default choice for tuple data structures which don't need the last bit of efficiency.
This is an almost line for line translation of the MSDN "Your First Windows Program" C demo:
(typedef LRESULT int-ptr-t)
(typedef LPARAM int-ptr-t)
(typedef WPARAM uint-ptr-t)
(typedef UINT uint32)
(typedef LONG int32)
(typedef WORD uint16)
(typedef DWORD uint32)
(typedef LPVOID cptr)
(typedef BOOL (bool int32))
(typedef BYTE uint8)
(typedef HWND (cptr HWND))
(typedef HINSTANCE (cptr HINSTANCE))
(typedef HICON (cptr HICON))
(typedef HCURSOR (cptr HCURSOR))
(typedef HBRUSH (cptr HBRUSH))
(typedef HMENU (cptr HMENU))
(typedef HDC (cptr HDC))
(typedef ATOM WORD)
(typedef LPCTSTR wstr)
(defvarl NULL cptr-null)
(typedef WNDCLASS (struct WNDCLASS
(style UINT)
(lpfnWndProc closure)
(cbClsExtra int)
(cbWndExtra int)
(hInstance HINSTANCE)
(hIcon HICON)
(hCursor HCURSOR)
(hbrBackground HBRUSH)
(lpszMenuName LPCTSTR)
(lpszClassName LPCTSTR)))
(defmeth WNDCLASS :init (me)
(zero-fill (ffi WNDCLASS) me))
(typedef POINT (struct POINT
(x LONG)
(y LONG)))
(typedef MSG (struct MSG
(hwnd HWND)
(message UINT)
(wParam WPARAM)
(lParam LPARAM)
(time DWORD)
(pt POINT)))
(typedef RECT (struct RECT
(left LONG)
(top LONG)
(right LONG)
(bottom LONG)))
(typedef PAINTSTRUCT (struct PAINTSTRUCT
(hdc HDC)
(fErase BOOL)
(rcPaint RECT)
(fRestore BOOL)
(fIncUpdate BOOL)
(rgbReserved (array 32 BYTE))))
(defvarl CW_USEDEFAULT #x-80000000)
(defvarl WS_OVERLAPPEDWINDOW #x00cf0000)
(defvarl SW_SHOWDEFAULT 5)
(defvarl WM_DESTROY 2)
(defvarl WM_PAINT 15)
(defvarl COLOR_WINDOW 5)
(deffi-cb wndproc-fn LRESULT (HWND UINT LPARAM WPARAM))
(with-dyn-lib "kernel32.dll"
(deffi GetModuleHandle "GetModuleHandleW" HINSTANCE (wstr)))
(with-dyn-lib "user32.dll"
(deffi RegisterClass "RegisterClassW" ATOM ((ptr-in WNDCLASS)))
(deffi CreateWindowEx "CreateWindowExW" HWND (DWORD
LPCTSTR LPCTSTR
DWORD
int int int int
HWND HMENU HINSTANCE
LPVOID))
(deffi ShowWindow "ShowWindow" BOOL (HWND int))
(deffi GetMessage "GetMessageW" BOOL ((ptr-out MSG) HWND UINT UINT))
(deffi TranslateMessage "TranslateMessage" BOOL ((ptr-in MSG)))
(deffi DispatchMessage "DispatchMessageW" LRESULT ((ptr-in MSG)))
(deffi PostQuitMessage "PostQuitMessage" void (int))
(deffi DefWindowProc "DefWindowProcW" LRESULT (HWND UINT LPARAM WPARAM))
(deffi BeginPaint "BeginPaint" HDC (HWND (ptr-out PAINTSTRUCT)))
(deffi EndPaint "EndPaint" BOOL (HWND (ptr-in PAINTSTRUCT)))
(deffi FillRect "FillRect" int (HDC (ptr-in RECT) HBRUSH)))
(defun WindowProc (hwnd uMsg wParam lParam)
(caseql* uMsg
(WM_DESTROY
(PostQuitMessage 0)
0)
(WM_PAINT
(let* ((ps (new PAINTSTRUCT))
(hdc (BeginPaint hwnd ps)))
(FillRect hdc ps.rcPaint (cptr-int (succ COLOR_WINDOW) 'HBRUSH))
(EndPaint hwnd ps)
0))
(t (DefWindowProc hwnd uMsg wParam lParam))))
(let* ((hInstance (GetModuleHandle nil))
(wc (new WNDCLASS
lpfnWndProc [wndproc-fn WindowProc]
hInstance hInstance
lpszClassName "Sample Window Class")))
(RegisterClass wc)
(let ((hwnd (CreateWindowEx 0 wc.lpszClassName "Learn to Program Windows"
WS_OVERLAPPEDWINDOW
CW_USEDEFAULT CW_USEDEFAULT
CW_USEDEFAULT CW_USEDEFAULT
NULL NULL hInstance NULL)))
(unless (equal hwnd NULL)
(ShowWindow hwnd SW_SHOWDEFAULT)
(let ((msg (new MSG)))
(while (GetMessage msg NULL 0 0)
(TranslateMessage msg)
(DispatchMessage msg))))))
Lisp doesn't have a tuple problem; it has a "clueless programmers blogging about it" problem.An ounce of lie does damage that a pound of truth is needed to repair.
Sad but true.
I haven't worked enough in a Lisp to encounter this problem. Rather, my issue is just that I really dislike the syntax and find Lisp code difficult to read.