
Lisp's mysterious tuple problem - AndrewBissell
https://www.codeproject.com/Articles/1186940/Lisps-Mysterious-Tuple-Problem
======
serichsen
Lisp's problem is rather that so many people play around with it, but not long
enough to develop /good taste/ in it, and finally loudly complain about the
language when their real problem is their lack of imagination.

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.

~~~
kazinator
Lisp's problem is that almost nobody plays with it, but they know that it's
that language that relies a lot on parentheses. When they do rarely come
across the odd example it is full of unfamiliar symbols: _car_ , _labels_ ,
_mapcan_ , _assoc_ , _setf_ and so on. Depending on the code, it's possible
that not a single thing means anything to even be able to guess.

------
KingMob
Hmm, not sure I agree with "[W]hile Clojure does have some nice syntax for
handling maps, which can be used to represent records, it's unfortunately
still pretty idiomatic to store tuples in lists in Clojure code."

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}

------
abc_lisper
TLDR: Items in lists don't have labels, and hence the code is hard to read.
Use maps instead.

------
flavio81
There is no other words to qualify this article:

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.

------
soberhoff
I don't think it's as easy as that. In fact there's probably no single reason
accountable for Lisp's lack of popularity. Here's my own personal pet peeve:
Declaring local variables creates a level of nesting. Local variables are a
great tool for improving code clarity. Having to wrap your logic with `(let
[value (...)] ...)` in order make a new local variable is unnecessarily
painful.

~~~
idyllei
The deal breaker for me is the unwillingness to rename outdated identifiers:
`car` instead of `first` or `head` and `cdr` instead of `rest` or `tail`; the
use of asterisks to show that a function differs in semantics (`let` versus
`let*`); and the verbosity of using anything other than a pure list.

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.)

~~~
serichsen
Common Lisp has “first” and “rest” as aliases for “car” and “cdr” where it
makes sense.

------
thisrod
I think that the author needs to learn about association lists. The
constructor (define point '((x 1) (y 2) (z 3))) is as clear as any other
language. Granted, (assoc 'x point) is a bit more verbose than point.x, but
you could extend Lisp with the syntax ('x point) if it matters. Does Arc do
that?

~~~
kbp
> I think that the author needs to learn about association lists. The
> constructor (define point '((x 1) (y 2) (z 3))) is as clear as any other
> language. Granted, (assoc 'x point) is a bit more verbose than point.x, but
> you could extend Lisp with the syntax ('x point) if it matters. Does Arc do
> that?

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.

------
m-j-fox
One idea that I think gives the best of both worlds is to use lisp to generate
your C++ (or whatever) boilerplate instead of dealing with the maddening
vaugeries of template metaprogramming, boost, STL, etc to achieve genericity.

~~~
deepaksurti
Something relevant: refactoring C++ using Common Lisp:
[https://www.youtube.com/watch?v=h31pURzgYX8](https://www.youtube.com/watch?v=h31pURzgYX8)

------
lispm
The author makes the claim that structures and CLOS classes are not widely
used. The opposite is true.

    
    
      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.

~~~
flavio81
Excellent post. Sometimes i get to think that CL is fully object oriented
beneath the surface.

------
kazinator
TXR Lisp, working with native Win32/Win64 "tuples" like WNDCLASS, POINT, MSG,
RECT and PAINTSTRUCT.

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.

~~~
flavio81
>Lisp doesn't have a tuple problem; it has a "clueless programmers blogging
about it" problem.

Sad but true.

------
draw_down
Tuples are also used in Python to express an immutable list (sorry for
imprecise wording there) of values.

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.

