[HN Gopher] Reading "A Programmer's Guide to Common Lisp"
       ___________________________________________________________________
        
       Reading "A Programmer's Guide to Common Lisp"
        
       Author : Tomte
       Score  : 163 points
       Date   : 2024-02-22 13:00 UTC (10 hours ago)
        
 (HTM) web link (journal.paoloamoroso.com)
 (TXT) w3m dump (journal.paoloamoroso.com)
        
       | ryan-duve wrote:
       | One place I get stuck all the time with Common Lisp is the REPL.
       | I'm used to IPython, which allows me to enter multiple lines and
       | execute them all at once, then go "up" and get all those lines
       | back and change something to fix the error:                   In
       | [1]: my_list = [1, 2, 3]            ...: my_other_list = [4, 5,
       | 6]            ...: final_list = [str(i) for i in my_list ++
       | my_other_list] # whoops a syntax error!            ...:
       | ...: "".join(final_list)         --------------------------------
       | -------------------------------------------         TypeError
       | Traceback (most recent call last)         Cell In[1], line 3
       | 1 my_list = [1, 2, 3]               2 my_other_list = [4, 5, 6]
       | ----> 3 final_list = [str(i) for i in my_list ++ my_other_list] #
       | whoops a syntax error!               5 "".join(final_list)
       | TypeError: bad operand type for unary +: 'list'              In
       | [2]: my_list = [1, 2, 3]            ...: my_other_list = [4, 5,
       | 6]            ...: final_list = [str(i) for i in my_list +
       | my_other_list]            ...:            ...:
       | "".join(final_list)         Out[2]: '123456'
       | 
       | All the CL REPLs I've tried only allowing getting back one line
       | at a time, which feels tedious to execute in order. I feel like
       | I'm fundamentally missing something about iterative development
       | in Common Lisp and it's blocking me from learning the language.
        
         | smatija wrote:
         | When I last used CL using Emacs+SLIME helped a lot. Especially
         | C-x C-e (evaluate under the cursor) and C-M-x (evaluate form
         | you are inside of). With this you get all the benefits of
         | classic editor together with REPL-like instant feedback.
         | 
         | https://slime.common-lisp.dev/
        
           | pamoroso wrote:
           | In a Lisp-aware editor, such as Emacs with SLIME, you can
           | send to the REPL for evaluation an arbitrary block of text
           | that contains multiple separate expressions. See for example
           | M-x slime-eval-region (C-c C-r ).
        
             | collinrapp wrote:
             | When I read "I feel like I'm fundamentally missing
             | something about iterative development in Common Lisp" in
             | the GP, I thought of exactly what's in these replies. I've
             | only recently started learning CL via Practical Common
             | Lisp, and while I liked Emacs+SLIME, I'm a vim guy (I know)
             | and switched to vim+VLIME instead, and so far I'm loving
             | it. This to me has actually been the "secret sauce" of Lisp
             | in my early experience, because now when I go to write code
             | or use the REPL for languages like Python and Ruby, I find
             | myself missing the SLIME/VLIME experience. I find it to be
             | a very intuitive and efficient way to write code
             | interactively.
        
         | Jtsummers wrote:
         | They typically read one expression at a time. Wrap your
         | multiline/multiexpression code in a let or progn expression or
         | similar if you want to write multiple expressions before
         | evaluating them.
         | 
         | They will normally let you write as many lines as needed for an
         | expression and then let you go back and edit the whole thing.
        
         | pjmlp wrote:
         | If you want something like IPython, you need to look into the
         | commercial survivors from Common Lisp, with a full blown IDE
         | experience, like Lisp Works and Allegro Common Lisp.
        
         | susam wrote:
         | Are you using the CL REPL directly on a terminal emulator? I do
         | not type expressions directly into the REPL often. I do
         | sometimes but not often. Instead I _send_ expressions right
         | from the editor to the REPL. The REPL itself is available as a
         | separate buffer in the editor. How this is done depends on the
         | editor /IDE. Some examples:
         | 
         | * With Emacs + SLIME, we can type C-M-x (evaluate current top-
         | level form) or C-x C-e (evaluate the expression before the
         | cursor).
         | 
         | * With Vim + Slimv, it is ,e and ,d respectively.
         | 
         | * Yet another popular option is Vim + Vlime in which case the
         | key sequences are \ss and \st instead.
         | 
         | I have written two guides to explain how to create such a
         | development environment from scratch and get started with such
         | a setup:
         | 
         | * https://github.com/susam/emacs4cl
         | 
         | * https://susam.net/lisp-in-vim.html
         | 
         | There are commercial implementation + IDEs too which according
         | to many people provide even better integrated development
         | experience. The most popular among them is perhaps LispWorks. I
         | use Emacs + SLIME myself.
        
           | ryan-duve wrote:
           | Yes, I was directly typing in the REPL. I think Slimv may
           | have been the thing I'm missing. When I'm back to my laptop I
           | will give it a shot. Thanks!
        
             | lispm wrote:
             | I would type into a REPL, but not in a terminal. Typically
             | one would run a REPL inside an editor or something similar.
             | The tool to run a REPL is usually called a "Listener" in
             | Lisp. For example I would use GNU Emacs, call SLIME and use
             | a SLIME REPL.
             | 
             | Lisp development in a terminal, without an editor, is not
             | so helpful.
        
         | neutronicus wrote:
         | In Lisp you'd do something like                   (let* ((my-
         | list '(1 2 3))                (my-other-list '(4 5 6))
         | (final-list (concatenate 'list my-list my-other-list)))
         | (whatever-cl-calls-join final-list ""))
         | 
         | This is a single Lisp form evaluated at the REPL that you could
         | get back and edit as necessary. It's been a decade or so haha
         | but that's the idea
        
           | ogogmad wrote:
           | Broad question about Lisp conventions: Why can't you just
           | define each variable in a separate line, instead of batching
           | those definitions up at the top? This seems like a confusing
           | break from convention. In C-derived languages, you can stick
           | variable definitions anywhere.
           | 
           | I've looked it up and Common Lisp doesn't seem to provide
           | such an option. Seems like an odd restriction. Oh, and the
           | difference between LET, LET* and LETREC is another weird
           | break from convention.
        
             | dwringer wrote:
             | You can do each variable on its own line with stuff like
             | "defvar" and "setf", but that will add them to the current
             | package namespace - and convention is to avoid polluting
             | namespaces with names that will be unused later. So, if the
             | variables are only used within a given scope, the scope is
             | explicitly delineated with a "let" block. It's not a
             | general requirement.
             | 
             | I'm not sure what you mean about the difference between LET
             | and LET* (the latter simply lets subsequent variable
             | declarations refer to previously declared variables in the
             | same block), and LETREC is not a builtin part of Common
             | Lisp.
        
               | ogogmad wrote:
               | I think SETF only reassigns a variable that's already
               | been declared. DEFVAR defines a _dynamically-scoped_
               | global variable, no? So why doesn 't Common Lisp let you
               | write `(VAR new-var new-val)` like every single other
               | language, and have it declare a variable in the _current
               | scope_? Unless there 's a good reason, this is yet
               | another obstacle to these languages being adopted by
               | anybody except the die-hards.
               | 
               | If you want to carefully delimit scope, doesn't Common
               | Lisp (and other Lisps) provide PROGN? Seems like a less
               | complicated approach.
               | 
               | > I'm not sure what you mean about the difference between
               | LET and LET* (the latter simply lets subsequent variable
               | declarations refer to previously declared variables in
               | the same block
               | 
               | Why does LET even exist as an alternative to LET*? Why
               | does Lisp even bother making this distinction?
        
               | pfdietz wrote:
               | Common Lisp doesn't have a top level lexical scope, which
               | seems to be what you're looking for here.
               | 
               | Putting something like VAR everywhere now causes issues
               | because it's not a form that returns a value. Also,
               | there's no place to hang declare forms on it. And if it's
               | executed conditionally, what does that mean? The var is
               | declared on one branch but not the other?
        
               | ogogmad wrote:
               | Why would a variable declaration be executed
               | conditionally, unless you can somehow GOTO past the
               | declaration? CL doesn't let you do that, does it?
               | 
               | > top level lexical scope
               | 
               | What does this mean? Every single other language lets you
               | write:                  var x = y;
               | 
               | Why is Lisp seemingly the only (imperative) language that
               | doesn't?
        
               | medo-bear wrote:
               | > Why would a variable declaration be executed
               | conditionally, unless you can somehow GOTO past the
               | declaration? CL doesn't let you do that, does it?
               | 
               | http://clhs.lisp.se/Body/s_tagbod.htm#tagbody
               | 
               | http://clhs.lisp.se/Body/s_go.htm#go
        
               | pfdietz wrote:
               | One reason to not allow this is that it breaks the
               | equivalence that (for example)                  (progn
               | <form>)
               | 
               | is equivalent to                   <form>
        
               | martinflack wrote:
               | ^ This is huge. A lot of top-level macro usage would be
               | really annoying to implement if wrapping a PROGN around
               | top-level commands neutered their effect.
        
               | HexDecOctBin wrote:
               | Why is this a problem for Common Lisp, and not for the
               | dozens of Scheme implementations that allow you to
               | (define ...) anywhere? (never mind what the RnRS standard
               | says)
               | 
               | > And if it's executed conditionally, what does that
               | mean? The var is declared on one branch but not the
               | other?
               | 
               | Here is some C code:                 if (cond()) {
               | preamble();           int a = 5;           do_stuff(a);
               | } else {           do_stuff(6);       }
               | 
               | And some Scheme code:                 (if (cond)
               | (begin               (preamble)               (define a
               | 5)               (do-stuff a))           (do-stuff 6))
               | 
               | So why can't Common Lisp have the equivalent?
               | (if (cond)           (progn               (preamble)
               | (var a 5)               (do-stuff a))           (do-stuff
               | 6))
               | 
               | Instead, CL forces you to do this:                 (if
               | (cond)           (progn               (preamble)
               | (let ((a 5))                   (do-stuff a)))
               | (do-stuff 6))
               | 
               | Do you see the problem? Common Lisp wants you to declare
               | every set of intermediate variables in a nested scope,
               | leading to super-deep nesting unless you start breaking
               | you function into small pieces for no reason (which then
               | hurts readability).
               | 
               | This is why languages die, when the old guard refuses to
               | see that there are better ways of doing things than what
               | they are used to.
        
               | medo-bear wrote:
               | > Do you see the problem?
               | 
               | No. Explicit scoping is a huge plus.
               | 
               | > This is why languages die, when the old guard refuses
               | to see that there are better ways of doing things than
               | what they are used to.
               | 
               | Don't mistake this for not being able to see beyond your
               | nose.
        
               | HexDecOctBin wrote:
               | So, every other language that doesn't create a new nested
               | scope for every consecutive group of intermediate
               | variables is doing it wrong? Which is to say, almost
               | every language apart from Common Lisp. Does that sound
               | reasonable to you?
        
               | kragen wrote:
               | while i'm not sure lisp's approach is better in this
               | case, i do think it's fairly common that 'almost every
               | language apart from ... lisp' 'is doing it wrong', so i
               | don't think that would be an unreasonable position to
               | hold on those grounds ;)
        
               | a-french-anon wrote:
               | You're talking about a special operator, a language
               | primitive. In that view, let is perfectly fine and clear.
               | Macros are what you want if syntactic sugar is needed.
        
               | medo-bear wrote:
               | I'd instead point out that an appeal to majority is a
               | basic fallacy.
               | 
               | edit:
               | 
               | Let me also add this: you cannot get away from scoping.
               | If you create intermediate values in your language of
               | choice you better understand the implicit (and sometimes
               | very complicated) scoping rules of that language. All
               | that Common Lisp does is it makes this scoping explicit.
        
               | Karrot_Kream wrote:
               | You've rediscovered another essential portion of Lisp:
               | the smug lisp weenie.
        
               | kazinator wrote:
               | > _So, every other language that doesn 't create a new
               | nested scope for every consecutive group of intermediate
               | variables is doing it wrong?_
               | 
               | In my opinion, it is a giant, flaming misfeature.
               | 
               | > _Which is to say, almost every language apart from
               | Common Lisp_
               | 
               | Algol; Ada; Modula 1, 2, and 3; Oberon; Eiffel; ...
        
               | pfdietz wrote:
               | Nesting is a general problem beyond let, so it deserves a
               | general solution. A solution in Lisp is the nest macro.
               | 
               | https://fare.livejournal.com/189741.html
        
               | dreamcompiler wrote:
               | > Instead, CL forces you to do this:                 (if
               | (cond)           (progn               (preamble)
               | (let ((a 5))                   (do-stuff a)))
               | (do-stuff 6))
               | 
               | Above would be unusual. I'd only write code that way if
               | preamble was dependent on some previous binding of _a_.
               | Note that _let_ subsumes _progn_ in most cases. I 'd
               | write it like this:                 (if (cond)
               | (let ((a 5))               (preamble)               (do-
               | stuff a))           (do-stuff 6))
        
               | shawn_w wrote:
               | Scheme only allows internal defines at the start of a
               | block, where they're all combined into a letrec (or at
               | least treated the same way). A few do allow define
               | anywhere; SRFI-245, which gives a formal description of
               | the semantics, lists 5 implementations that do, including
               | some popular ones, but it's hardly dozens.
               | 
               | Racket also allows it and encourages it in the style
               | guide, using the "less nesting" (and thus shallower
               | indentation ) rationale you bring up.
               | 
               | https://srfi.schemers.org/srfi-245/srfi-245.html
        
               | lispm wrote:
               | > And some Scheme code:                   (if (cond)
               | (begin               (preamble)               (define a
               | 5)               (do-stuff a))           (do-stuff 6))
               | 
               | This is not defining a local variable in Scheme. The
               | various Scheme standards also require that DEFINE appears
               | at the top of a body.
               | 
               | > Common Lisp wants you to declare every set of
               | intermediate variables in a nested scope
               | 
               | Lisp programmers edit code by list operations. For
               | example, I can set the cursor between (preamble) and the
               | (let ...) form. control-meta-t transposes the lists. The
               | let form is moved upwards and the enclosed body is moved,
               | too. Try that in the C code. Code transformations are
               | vastly easier with explicit scopes.
               | 
               | JavaScript has introduced a LET for a reason: block scope
               | is clearer than VAR (function / global scope).
               | (defun foo (a)           (if a             (var b a))
               | (print b))   ; what's the value here?
               | 
               | In Common Lisp above would not be valid.
               | 
               | I would need to write:                  (defun foo (a)
               | (let (b)            (if a               (setf b a))
               | (print b)))
               | 
               | I can then immediately see that each B is inside a LET
               | scope, which defines it. It's just by simply moving
               | upwards in the expression tree. I would not need to
               | search the whole expression tree. Also languages may do
               | different things with definitions inside conditional
               | expressions.
        
               | kazinator wrote:
               | Since (preamble) cannot possibly perform a side effect
               | which affects the initializing expression 5, it can be
               | moved:                 (if (cond)         (let ((a 5))
               | (preamble)           (do-stuff a))         (do-stuff 6))
               | 
               | You can work in side effects into the expressions of a
               | let or let*:                 (let* ((x (progn (widget-
               | lock w)                        (widget-x w)))
               | (y ...))          (...)          (widget-unlock w))
               | 
               | If lock-widget returns the widget, it can look like this:
               | (let* ((w (widget-lock w))              (x (widget-x x))
               | (y ...))         ...         (widget-unlock w))
        
               | kazinator wrote:
               | TXR Lisp has a form of unhinged let in the "opip syntax"
               | that underlies all of its threading macros.
               | 
               | https://www.nongnu.org/txr/txr-manpage.html#S-F2CAF1CB
               | 
               | For instance                 (flow 1         (+ 2)
               | (let x) ;; x is 3 now         (+ 3)         (let y) ;; y
               | is 6         (+ 4)   ;; pipeline value is 10 now
               | (+ x y)) ;; 19 returned: (+ 3 6 10)
               | 
               | There is a (let (var1 init1) (var2 init2) ...) syntax
               | supported also. Both these variants act as pass-through
               | pipe elements: they bind variables that are in scope of
               | the rest of the pipe.
               | 
               | However, if you use the normal (let ((var init) ...) ...)
               | syntax, then that is not special any more; it is just the
               | regular let being threaded, like any other operator. It
               | does not bind variables visible to the rest of the pipe.
               | 
               | Outside of this, there are only _let_ and _let*_ which
               | resemble the Common Lisp ones.
        
               | lispm wrote:
               | Programming languages may let you define variables by
               | assigning them. But the scope can differ across
               | languages.
               | 
               | Common Lisp requires one to clearly define the scope.
               | PROGN does not create a scope.
               | 
               | > Why does LET even exist as an alternative to LET*? Why
               | does Lisp even bother making this distinction?
               | 
               | Because there is a scope difference.
               | 
               | One can always do                   (let (a b c)
               | (setf a 10)           ...           (setf b 20)
               | ...           (setf c (+ a b)           ...)
               | 
               | Think of                    (let* ((a 10) (b 20) (c (+ a
               | b)))             ...)
               | 
               | as a short form for                    ((lambda (a b)
               | ((lambda (c)                ...)              (+ a b)))
               | 10 20)
        
               | ogogmad wrote:
               | I know what the difference is between LET and LET*. What
               | you haven't provided is a motivation for forcing the
               | programmer to worry about that. It seems there's no
               | concrete example of where unstarred LET would be better.
               | If a programmer ever falls in the habit of sometimes
               | using unstarred LET, then it's likely he'll make a
               | mistake by using it where starred LET* was the right
               | thing.
               | 
               | Even Scheme gives this distinction an odd prominence
               | that's not found outside the Lisp family. It seems
               | reasonable that when I write code, I shouldn't have to
               | stop and worry about which of the gazillion different LET
               | forms is appropriate, especially in a high-level language
               | which is supposed to help me write code (or read code)
               | without worrying about irrelevant details like that.
        
               | nanna wrote:
               | I think that the distinction makes the code clearer to
               | read. LET tells a future reader that they don't need to
               | bother scanning each variable binding for parent
               | variables, whereas LET* tells them that they do. Seems
               | like the same logic behind having a WHEN and UNLESS as
               | opposed to just an IF, the former meaning that one
               | needn't search for an 'else' block. The LISP family
               | encourage good style.
        
               | kragen wrote:
               | i'm not really sure myself, but i can think of some minor
               | advantages
               | 
               | with `let` you can say                   (let ((x y) (y
               | x)) ...
               | 
               | without worrying about the ordering of the variable
               | bindings. this is more interesting for dynamically-scoped
               | variables; in emacs lisp, for example, you might want to
               | say                   (let ((case-fold-search t) (outer-
               | case-fold-search case-fold-search))            ...
               | (let ((case-fold-search outer-case-fold-search)) (f))
               | ...)
               | 
               | so that when you call `f` it doesn't see your case-fold-
               | search binding
               | 
               | with `let*` you implicitly have an _execution sequence_
               | over the binding forms, but in most cases that 's
               | something you're specifying by accident. `let` strongly
               | suggests to the reader that she can consider any one of
               | the binding forms in the list in isolation; she doesn't
               | have to read through the first n-1 bindings to understand
               | the nth one
               | 
               | it's true that, in most cases, all three of them do the
               | same thing, and this is not the most parsimonious
               | approach
        
               | reikonomusha wrote:
               | I think you're overstating a supposed worry. I could
               | equally say, "why are all of these languages making me
               | worry about which scope a VAR is attached to?" In either
               | case, it doesn't seem to actually be a worry, it's just
               | different than what you're used to day-to-day.
               | 
               | Lisp's design, I find, is best understood through the
               | lens of how we might most straightforwardly interpret the
               | semantics of the syntax, i.e., how we might write an
               | evaluator for the language. It's a large part of the
               | appeal of using Lisp; it's possible to understand the
               | language--from parsing to execution--so well that you
               | could in principle write a conforming interpreter for
               | even something like Common Lisp without Herculean effort.
               | Having such an understanding of the language has a
               | variety of practical benefits even if you're just using
               | the language.
               | 
               | A construct that you suggest like                   (VAR
               | <variable> <value>)
               | 
               | would be quite laborious to nail down semantically,
               | especially if we want VAR to work in as many contexts as
               | possible. This goes against the above ethos.
               | 
               | Even then, if we do make a variety of decisions about
               | syntax and scoping rules to allow VAR, we have additional
               | questions to answer in the context of Lisp specifically.
               | For example, are macros allowed to expand into VAR
               | statements? Is this allowed?                   (defmacro
               | bind ()           '(var x 5))              (defun f (y)
               | (bind)           (+ x y))
               | 
               | This sort of shenanigan can't happen with LET quite as
               | opaquely, since the closest equivalent would require the
               | sum to be wrapped:                   (defmacro bind
               | (&body b)           `(let ((x 5))              ,@b))
               | (defun f (y)           (bind            (+ x y)))
               | 
               | Now I'm clued in to something goofy potentially happening
               | when looking at the definition of F.
               | 
               | So we might ban macros expanding into VAR statements. So
               | then how do we write binding macros? Introduce a
               | dedicated SCOPE special operator?
               | (defmacro bind (&body b)           `(scope
               | (var x 5)              ,@b))              (defun f (y)
               | (bind            (+ x y)))
               | 
               | But now we just have an obscure LET. :)
               | 
               | Languages with very complicated grammars also typically
               | come with comparatively complicated scoping rules (e.g.,
               | Python). Lisp's LET maybe seems gratuitous in a world of
               | "var x = 2" syntax, but it's also abundantly clear what
               | it means and how it works at all times, regardless of
               | nesting or context.
               | 
               | To leave the reader with one last thing to ponder: What
               | should this do in a compiled implementation of Common
               | Lisp with VAR?                   (defun what? ()
               | (tagbody               (if (= 0 (random 2))
               | (go :bind)                   (go :print))
               | :bind               (var x 1)                  :print
               | (print x)))
        
               | ogogmad wrote:
               | One possible alternative is to combine PROGN with LET,
               | which you might then call BLOCK, with the macro
               | definition of BLOCK looking for `VAR`s. The VAR syntax
               | wouldn't make sense anywhere else. More exactly:
               | (block          (var first-thing 'EXAMPLE)          (do-
               | something)          (var foo 'EXAMPLE)          (do-
               | something-else))
               | 
               | Expand this into:                 (let ((first-thing
               | 'EXAMPLE))           (do-something)           (let ((foo
               | 'EXAMPLE'))              (do-something-else)))
               | 
               | This would reduce indentation. But it would also involve
               | big changes to Common Lisp.
        
               | Jtsummers wrote:
               | It wouldn't require any changes to the language. It would
               | probably require a tree-walking macro, which are not too
               | uncommon. You'd want a different name though since
               | `block` is already a thing in CL.
               | 
               | http://clhs.lisp.se/Body/s_block.htm
               | 
               | I'd recommend _On Lisp_ by Paul Graham and _Let Over
               | Lambda_ by Doug Hoyt if you 're interested in trying it.
        
               | reikonomusha wrote:
               | If BLOCK weren't already the name of something in Common
               | Lisp, this sort of syntax is very easy to add, easy
               | enough to write in an HN comment. Let's call it OGOGMAD
               | instead.                   (defmacro ogogmad (&body b)
               | (cond             ((endp b) '(progn))             ((endp
               | (rest b)) b)             (t              (let ((f (first
               | b))                    (r (rest b)))                (if
               | (eq 'var (first f))                    `(let (,(rest f))
               | (ogogmad ,@r))                    `(progn
               | ,f                       (ogogmad ,@r))))))
               | 
               | Exactly as you specify, this only lets you put VAR syntax
               | immediately inside of OGOGMAD forms. (Untested, typed on
               | mobile.)
        
               | lispm wrote:
               | Reducing indentation is mostly a non-goal in Lisp. Can't
               | work with nested lists? Don't use Lisp. Lisp processes
               | nested lists. Lisp means List Processor.
               | 
               | You may want to do it in a text oriented language, but
               | not so much in an expression oriented language, where
               | programmers edit an expression tree, by tree manipulation
               | commands.
               | 
               | In Lisp the "tree" is "nested lists".
               | 
               | Btw., what does                   (block          (when
               | (foo)           (var first-thing 'EXAMPLE))          (do-
               | something)          (var foo 'EXAMPLE)          (do-
               | something-else))
               | 
               | mean? Is it legal? Your macro would need to traverse the
               | expression tree, knowing the whole Lisp syntax, including
               | expanding macros, possibly in a source interpreter. WHEN
               | is a macro. BLOCK would need to find the VAR expression
               | inside the WHEN macro, which is not easy for the general
               | case. This would be very very strange. Macros are usually
               | expanded outside to inside. Your BLOCK macro would need
               | to expand macros in enclosed code, to find VAR
               | expressions.
               | 
               | In JavaScript something like above is legal in a
               | function.
        
               | samatman wrote:
               | Lua throws an error when you try and do analogous things.
               | I think it's compile time, actually, might be runtime
               | though. It would be reasonable for a language like Common
               | Lisp to reject potentially-undefined variables.
        
               | dannymi wrote:
               | >It seems there's no concrete example of where unstarred
               | LET would be better.
               | 
               | The unstarred let is a destructuring bind of an entire
               | tuple.
               | 
               | For example (let ((x 1) (y 2) (z 3)) ...) does the
               | entirety of x := 1, y := 2, z := 3 at once, the right
               | hand sides in the old frame and the left hand sides in
               | the new frame.
               | 
               | So let introduces a new frame BUT only after all three
               | substitutions are done.
               | 
               | For example in order to rotate x y z (from the
               | surrounding frame) to the right:                   (let
               | ((y x) (z y) (x z))           ...)
               | 
               | means:                   ((lambda (y z x)            ...)
               | x y z)
               | 
               | For example:                   (let ((x 1) (y 2) (z 3))
               | (let ((y x) (z y) (x z))             (list x y z)))
               | => (3 1 2)
               | 
               | The starred let is the weird form. It's shorthand for
               | having another let in the tail each time: With A, B, C
               | each standing for a form:                   (let* (A B C)
               | ...) is defined to be (let (A) (let* (B C) ...). And so
               | on. (let* (C) ...) is (let (C) ...), the base case.
               | (let* (A B C) ...) is (let (A) (let (B) (let (C) ...)))).
               | Does that look natural to you?
               | 
               | (let (_) 5) is valid too.
               | 
               | >If a programmer ever falls in the habit of sometimes
               | using unstarred LET, then it's likely he'll make a
               | mistake by using it where starred LET* was the right
               | thing.
               | 
               | Never happened to me so far and I'm using Lisp for 8
               | years now.
               | 
               | >It seems reasonable that when I write code, I shouldn't
               | have to stop and worry about which of the gazillion
               | different LET forms is appropriate
               | 
               | You do you, but you seem to think that this is an
               | aesthetic choice rather than what the mathematics
               | automatically gives. Lisp was not really designed, it was
               | discovered. As long as you don't break any of the
               | mathematical properties, go ahead and make it like you
               | want it to be.
               | 
               | One thing you could safely do is remove the automatic
               | tuple destructuring on let, basically removing the
               | ability to have parallel-track bindings. Then you'd end
               | up with something like Haskell:                   main =
               | let x = 1                    y = 2                    z =
               | 3                    in let y = x
               | z = y                           x = z
               | in x         => <<loop>>
        
               | ogogmad wrote:
               | Python expresses destructuring bind using:
               | x, y = foo, bar
               | 
               | It occasionally comes in handy. It certainly does suggest
               | there's compatibility with VAR syntax.
               | 
               | Haskell's `do` notation also supports destructuring bind
               | the Python way. It's even made into a special case of
               | pattern matching. It also allows you to make declarations
               | anywhere inside a block. This seems better than the
               | Common Lisp approach.
        
               | dannymi wrote:
               | > It[Python] certainly does suggest there's compatibility
               | with VAR syntax.
               | 
               | Maybe. Maybe not.
               | 
               | >It[Haskell, or do notation] also allows you to make
               | declarations anywhere inside a block.
               | main = do             let x = 1             let y = 2
               | let z = 3             let y = x                 z = y
               | x = z             print x              => compilation
               | error[1]
               | 
               | Do you find that normal?
               | 
               | How would you write what I wrote in Lisp in Haskell (for
               | example using what you said)? Is it gonna need new
               | concepts?
               | 
               | Compare:                   main = do             let x =
               | 1             let y = 2             let z = 3
               | let y = x             let z = y             let x = z
               | print [x, y, z]              => [1, 1, 1] (as expected--
               | but it's still dumb)
               | 
               | [1]                   a.hs:9:5: error:             *
               | Ambiguous type variable 'a0' arising from a use of
               | 'print'               prevents the constraint '(Show a0)'
               | from being solved.               Probable fix: use a type
               | annotation to specify what 'a0' should be.
               | These potential instances exist:                 instance
               | Show Ordering -- Defined in 'GHC.Show'
               | instance Show a => Show (Maybe a) -- Defined in
               | 'GHC.Show'                 instance Show Integer --
               | Defined in 'GHC.Show'                 instance Show () --
               | Defined in 'GHC.Show'                 instance (Show a,
               | Show b) => Show (a, b) -- Defined in 'GHC.Show'
               | instance (Show a, Show b, Show c) => Show (a, b, c)
               | -- Defined in 'GHC.Show'                 instance (Show
               | a, Show b, Show c, Show d) => Show (a, b, c, d)
               | -- Defined in 'GHC.Show'                 instance (Show
               | a, Show b, Show c, Show d, Show e) =>
               | Show (a, b, c, d, e)                   -- Defined in
               | 'GHC.Show'                 instance (Show a, Show b, Show
               | c, Show d, Show e, Show f) =>
               | Show (a, b, c, d, e, f)                   -- Defined in
               | 'GHC.Show'                 instance (Show a, Show b, Show
               | c, Show d, Show e, Show f,                           Show
               | g) =>                          Show (a, b, c, d, e, f, g)
               | -- Defined in 'GHC.Show'                 instance (Show
               | a, Show b, Show c, Show d, Show e, Show f, Show g,
               | Show h) =>                          Show (a, b, c, d, e,
               | f, g, h)                   -- Defined in 'GHC.Show'
               | instance (Show a, Show b, Show c, Show d, Show e, Show f,
               | Show g,                           Show h, Show i) =>
               | Show (a, b, c, d, e, f, g, h, i)                   --
               | Defined in 'GHC.Show'                 instance (Show a,
               | Show b, Show c, Show d, Show e, Show f, Show g,
               | Show h, Show i, Show j) =>                          Show
               | (a, b, c, d, e, f, g, h, i, j)                   --
               | Defined in 'GHC.Show'                 instance (Show a,
               | Show b, Show c, Show d, Show e, Show f, Show g,
               | Show h, Show i, Show j, Show k) =>
               | Show (a, b, c, d, e, f, g, h, i, j, k)
               | -- Defined in 'GHC.Show'                 instance (Show
               | a, Show b, Show c, Show d, Show e, Show f, Show g,
               | Show h, Show i, Show j, Show k, Show l) =>
               | Show (a, b, c, d, e, f, g, h, i, j, k, l)
               | -- Defined in 'GHC.Show'                 instance (Show
               | a, Show b, Show c, Show d, Show e, Show f, Show g,
               | Show h, Show i, Show j, Show k, Show l, Show m) =>
               | Show (a, b, c, d, e, f, g, h, i, j, k, l, m)
               | -- Defined in 'GHC.Show'                 instance (Show
               | a, Show b, Show c, Show d, Show e, Show f, Show g,
               | Show h, Show i, Show j, Show k, Show l, Show m, Show n)
               | =>                          Show (a, b, c, d, e, f, g, h,
               | i, j, k, l, m, n)                   -- Defined in
               | 'GHC.Show'                 instance (Show a, Show b, Show
               | c, Show d, Show e, Show f, Show g,
               | Show h, Show i, Show j, Show k, Show l, Show m, Show n,
               | Show o) =>                          Show (a, b, c, d, e,
               | f, g, h, i, j, k, l, m, n, o)                   --
               | Defined in 'GHC.Show'                 instance Show a =>
               | Show (Solo a) -- Defined in 'GHC.Show'
               | instance Show Bool -- Defined in 'GHC.Show'
               | instance Show Char -- Defined in 'GHC.Show'
               | instance Show Double -- Defined in 'GHC.Float'
               | instance Show Float -- Defined in 'GHC.Float'
               | instance Show Int -- Defined in 'GHC.Show'
               | instance Show Word -- Defined in 'GHC.Show'
               | instance Show a => Show [a] -- Defined in 'GHC.Show'
               | ...plus 13 instances involving out-of-scope types
               | instance Show GHC.Stack.Types.CallStack -- Defined in
               | 'GHC.Show'                   instance Show
               | GHC.Types.KindRep -- Defined in 'GHC.Show'
               | instance Show GHC.Types.Levity -- Defined in 'GHC.Show'
               | instance Show GHC.Types.Module -- Defined in 'GHC.Show'
               | instance Show GHC.Num.Natural.Natural -- Defined in
               | 'GHC.Show'                   instance Show a => Show
               | (GHC.Base.NonEmpty a)                     -- Defined in
               | 'GHC.Show'                   instance Show
               | GHC.Types.RuntimeRep -- Defined in 'GHC.Show'
               | instance Show GHC.Stack.Types.SrcLoc -- Defined in
               | 'GHC.Show'                   instance Show
               | GHC.Types.TrName -- Defined in 'GHC.Show'
               | instance Show GHC.Types.TyCon -- Defined in 'GHC.Show'
               | instance Show GHC.Types.TypeLitSort -- Defined in
               | 'GHC.Show'                   instance Show
               | GHC.Types.VecCount -- Defined in 'GHC.Show'
               | instance Show GHC.Types.VecElem -- Defined in 'GHC.Show'
               | * In a stmt of a 'do' block: print x               In the
               | expression:                 do let x = 1
               | let y = 2                    let z = 3
               | let y = x                        z = y
               | ....                    ....               In an equation
               | for 'main':                   main                     =
               | do let x = ...                          let y = ...
               | let z = ...                          ....           |
               | 9 |     print x           |     ^^^^^
               | 
               | ^ Are you sure you want this in your language? :)
        
               | tome wrote:
               | > How would you write what I wrote in Lisp in Haskell
               | main = do             let x = 1             let y = 2
               | let z = 3             let y' = x                 z' = y
               | x' = z             print [x', y', z']
        
               | martinflack wrote:
               | You might like Guile, a form of Scheme. It lets you have
               | define calls inside a begin call (progn equivalent)
               | exactly as you ask.
        
             | kragen wrote:
             | i'm not sure it's correct to say that lisp's 01959 approach
             | to defining variables is a 'confusing break from
             | conventions' established by c, which originated in 01972
             | but didn't support 'sticking variable definitions anywhere'
             | until 01999 with the 01999 revision of the iso c standard.
             | i would rather say that sticking variable declarations
             | anywhere, a feature i believe was introduced to the
             | mainstream by c++ around 01990, was the break from
             | conventions. almost all the languages i'm familiar with
             | from before that required you to batch up local variable
             | declarations at the tops of scopes: c, smalltalk, pascal,
             | scheme
             | 
             | the exception is basic, where you could defint i or dim
             | x(128) at any point, but it would be an understatement to
             | say that basic's conventions were not widely emulated by
             | other languages
        
               | kragen wrote:
               | also basic variables are global. most of these languages
               | let you create global variables anywhere, or at least
               | anywhere outside of a subroutine
        
               | gknoy wrote:
               | I hope this doesn't derail the conversation much, but I
               | found it interesting that you seem to be using 5 digit
               | years, a convention I've never seen before. What drives
               | this choice?
        
               | kragen wrote:
               | it turns out it's sort of like wearing a mohawk. a long
               | now mohawk. it's a completely harmless deviation from
               | convention which provokes an amazing variety of
               | reactions, some astonishingly aggressive, while other
               | people react thoughtfully
        
               | michael1999 wrote:
               | Thank you. I hadn't drawn the parallel before. But that
               | matches my experience with both.
        
             | avgcorrection wrote:
             | > In C-derived languages, you can stick variable
             | definitions anywhere.
             | 
             | ANSI C89 requires that variable declarations only occur at
             | the beginning of the scope.
        
             | kazinator wrote:
             | Mixed declarations and statements appeared in C99. C90
             | didn't have them.
             | 
             | Other Algol-like languages including Algol itself also
             | separate declarations and statements: Pascal, Modula, Ada,
             | ...
             | 
             | In C90, you can have variables anywhere, but they must be
             | wrapped in a compound statement. This has a number of
             | advantages:
             | 
             | 1. You can repeat variables:                  {
             | int yes = 1;          setsockopt(fd, SOL_SOCKET,
             | SO_WHATEVER, &yes);        }             {           int
             | yes = 1;          setsockopt(fd, SOL_SOCKET, SO_OTHER,
             | &yes);        }
             | 
             | 2. You can move code around more easily, and move these
             | sub-blocks into their own functions more easily, since they
             | follow "declaration close to use".
             | 
             | 3. You control the _end_ of the scope of the variables, not
             | only the beginning. You know that after each of the above
             | two closing braces, the yes variable is no longer in scope.
             | (If there is a yes variable in scope, it must be coming
             | from a parent scope; it is not  "leaking" sideways.)
             | 
             | 4. If you write a _goto_ which goes around these
             | encapsulated scopes, that goto does not jump into a region
             | where the variable is uninitialized. Compare:
             | void fn()        {           goto end;           {
             | int x = 42;           }           end: ;                //
             | x is not in scope here, OK        }                  void
             | fn()        {           goto end;           int x = 42;
             | end: ;           // x is in scope here, but not
             | initialized!        }
             | 
             | For these reasons, I avoid mixing declarations and
             | statements in C programming; the separation is a very good
             | idea. It's enforced in Ada, where it is rationalized with
             | safety arguments. Mixing declarations and statements
             | encourages bugs. It lets you hack dubious solutions into
             | the program without having to think about how its structure
             | could be improved to do it cleanly.
             | 
             | In Common Lisp and similar dialects, you can mix imperative
             | code with variable definitions by using _progn_.
             | (let ((x (get-x-object)             (y (progn (perform-
             | side-effect)                       (get-y-object)))
             | (z (get-z-object)))         ...)
             | 
             | On a small number of occasions, I've done the above in C,
             | using the comma operator, to avoid mixed declarations and
             | statements:                 int x = get_x();       int y =
             | (perform_side_effect(), get_y());
             | 
             | It's a little dirty but not as dirty as mixed declarations
             | and statements.
             | 
             | Also note that there is no need for mixed declarations and
             | statements, if your code is functional! In any of your code
             | that emphasizes functional programming, you should not run
             | into a need for this, because the entire purpose of the
             | feature is to be able to stick a side effect between
             | variable definitions, which has to be sequenced there.
             | 
             | The Scheme language has the feature of defining variables
             | anywhere. You can use the _define_ form in any scope, like
             | inside a function:                 (define (fun arg)
             | (define x 0)         (define y 1)         ...)
             | 
             | How this works is that Scheme implementations perform a
             | code walk which transforms these defines into nested _let_
             | forms. Each sequential body of expressions has to be
             | scanned for the presence of define and treated this way.
             | 
             | It's pretty ugly; you end up with what _looks_ like a self-
             | contained form (define x 0) that is frobbing the
             | surrounding lexical scope, such that another form not
             | enclosed in it depends on its definition of x.
             | 
             | In Common Lisp and related dialects, anything that starts
             | with "(def" is understood to be for top-level definitions
             | only, which work by performing a run-time side effect when
             | they are evaluated. (And so their effect is visible to
             | later forms not due to lexical scope but due to the
             | chronological order of execution.)
             | 
             | There is a strong metaprogramming advantage in having rigid
             | variable binding forms like let. Because _let_ has all the
             | variables in one place, it is easy to interpret and
             | compile. This is one of the things that helps a Lisp-in-
             | Lisp metacircular evaluator be very short. If you have
             | variables defined anywhere, it means that every form that
             | contains statement-like forms must be scanned for those
             | definitions. They are not in a fixed place in the AST: (let
             | _vars_ _form_ ...). That is one big reason why if you
             | implement Scheme 's _define_ , you want that to be expanded
             | early, so that everything downstream just sees nice nested
             | _let_ s.
        
             | db48x wrote:
             | Lisp existed before any of the conventions of languages
             | like C were invented.
        
             | mtlmtlmtlmtl wrote:
             | Typically the convention is to introduce a new scope with
             | let. You can, if you really want to, declare the names at
             | the top and then assign values later, similar to old C,
             | though this is not idiomatic:                 (let (foo bar
             | baz)         (setf foo 2)         (other-code)
             | (setf bar (expt foo 2))         (more-code)         (setf
             | baz 5))
        
         | behnamoh wrote:
         | The sibling comments seem to interpret your need in a lispy
         | way! In IPython, you could use arrow keys to go back in history
         | and then edit the code you want and execute it. It seems you're
         | saying that CL doesn't have this feature. But what the
         | commenters say is you don't probably need this feature anyway
         | because you can write the code you want in the editor and send
         | it to REPL. Python's REPL has this feature too, but obviously
         | it's not a "clean" approach because sometimes we just want to
         | experiment with things and don't want to pollute the main code
         | with these one-off expressions.
        
           | kragen wrote:
           | i think it would be more accurate to gloss the sibling
           | comments as saying that the place where lisps put this
           | feature is in emacs rather than in ipython; that is, emacs is
           | the lisp equivalent of ipython
           | 
           | python's built-in repl also lacks the desired feature, and
           | it's kind of a pain in the ass, but the ^o keybinding can go
           | some distance to compensating for it; when you use | or ^r to
           | get back to a desired line in history that begins a multiline
           | block, after editing it, type ^o instead of enter, and the
           | next line will appear below. works in bash too, and it's
           | super common in my experience to want to run multiple
           | historical commands in sequence instead of just one
           | 
           | jupyter notebook is maybe a better alternative to the emacs
           | feature. darius bacon's halp provides a sort of notebook-like
           | feature in emacs
        
           | ryan-duve wrote:
           | I kind of figured I was approaching this wrong. If that
           | approach was useful, I would have found someone already
           | implemented it. One silly hangup I have is I am more
           | comfortable in Vim than Emacs, and I am trying to isolate my
           | learning to a language and not a language-plus-an-editor.
           | This must be how people feel when I suggest they switch to
           | Vim to make a certain workflow easier :-)
           | 
           | My goal isn't really to get "IPythonButForCommonLisp", but to
           | iteratively build up programs so I can quickly learn syntax.
           | I'm going to go with the Slimv suggestion the next chance I
           | get and see if that solves it. Instead of working in
           | revisioned code, I'll probably just work in `Untitled.lisp`
           | until I get the hang of things.
        
         | fiddlerwoaroof wrote:
         | So, I use the CL repl in emacs pretty heavily (SLIME) and M-p /
         | M-n do the thing you're talking about ipython doing. You can
         | also use the arrow keys to navigate to a previous expression
         | and hit enter to copy the whole expression to the current
         | input.
        
         | dhbradshaw wrote:
         | To get a nice combination of easy edits and evaluation in a
         | format familiar to a pythonista, could play with Lisp in
         | Jupyter:
         | 
         | https://github.com/yitzchak/common-lisp-jupyter
        
         | gumby wrote:
         | Well, just begin your typing with `(progn ` and when you've
         | typed the lines you want just press `)`.
         | 
         | And/or run your repl inside emacs and you'll have your whole
         | history available right there.
        
         | jrvarela56 wrote:
         | I learned a trick from Clojure that helps with this (they call
         | them rich comments).
         | 
         | You never type into the REPL but include comment sections
         | across your programs where you write code as it's intended to
         | be used/executed and then use key bindings to highlight/run in
         | your REPL.
         | 
         | This allows you write several lines and highlight them in order
         | to run them.
        
         | whartung wrote:
         | CLISP uses READLINE, and it's "form" based, so if you have a
         | multi-line form, an up-arrow, you get the entire form.
         | 
         | SBCL doesn't have anything that I'm aware off, neither does
         | CCL.
         | 
         | I've seen mentioned of wrapping SBCL in a readline wrapper.
         | There's a program that essentially gives readline behavior to
         | anything that reads stdin, a readline interface. It may be name
         | something clever like "readline", I've forgotten. I've never
         | used it.
         | 
         | The terminal experience is weak on those Lisps simply because
         | of the dominance emacs has in this space. Wrap SBCL or CCL in
         | Slime and you get readline and more. There's simply little
         | demand for a more functional CLI when the emacs/slime combo is
         | so powerful and useful.
         | 
         | The burden for Slime and emacs (for this use case) is actually
         | quite low. Both are pretty easy to install, modern emacs out of
         | the box works with simple mouse gestures and arrow keys, so you
         | don't need to be an emacs wonk to use it. And Slime has its own
         | dropdown menu for most tasks.
         | 
         | Readline in CLISP is useful, it makes CLISP orders of magnitude
         | more useable than raw SBCL. Cutting and pasting S-Exprs is just
         | not a great experience for routine work, IMHO. One advantage of
         | readline over the emacs buffers is that when you up-arrow, you
         | get the form. In the buffer, if you up-arrow you go up one
         | line. Mildly annoying when your last form spat out a 1000
         | lines. (That's why you search instead, but, nit noted.) With
         | readline your REPL experience is more like the shells.
         | 
         | And that may all work with the readline wrapper, but the
         | wrapper may well not be aware of S-exprs, so if you enter a
         | multi line expression and up-arrow you may get just the last
         | line of your last expression. Kind of worst of both world. But,
         | I'm just supposing here, I've not used it.
         | 
         | I'm quite content with CLISP (which does not have a lot of
         | modern activity on it), I just wish I could get it with SSL.
         | This seems to be some grand challenge I have not found a top-
         | of-first-page "SSL in Clisp" solution for on google.
         | 
         | Anyway, enough rambling.
         | 
         | Install emacs and slime.
        
           | mtreis86 wrote:
           | >There's a program that essentially gives readline behavior
           | to anything that reads stdin, a readline interface.
           | 
           | rlwrap https://man.archlinux.org/man/rlwrap.1
        
         | lispm wrote:
         | Lisp is not "line oriented", but "expression oriented". Lisp is
         | a "List Processor", not a "Text Processor". If you want to
         | group expressions in a REPL into one expression, use PROGN (or
         | similar).
         | 
         | The R in REPL stands for READ, which is a Lisp function, which
         | reads an expression and returns data.
        
       | mark_l_watson wrote:
       | I love it that Paolo says that Medley is his preferred
       | environment. I try Medley periodically, and for me in modern
       | times, Emacs with SBCL or LispWorks fits my needs better.
       | 
       | I was fortunate enough to have had a Xerox 1108 Lisp Machine
       | purchased for me in 1982. I loved it with InterLisp-D but a few
       | years later I started running it in Common Lisp mode, and the 1.5
       | megabytes of RAM in my 1108 was not really adequate.
       | 
       | In any case, the Medley developers make it easy to try Medley so
       | give it a try.
        
         | andsoitis wrote:
         | > Emacs with SBCL or LispWorks fits my needs better.
         | 
         | Do you use both, rather than one predominantly?
        
           | mark_l_watson wrote:
           | I usually use Emacs with a console save of LispWorks Pro,
           | sometimes Emacs+SBCL, sometimes the LispWorks Pro IDE.
           | 
           | EDIT: the reason why I usually use a LispWorks console save
           | instead of SBCL is obscure: I often ingest very large text
           | files, and the last time I checked a few years ago, LW was
           | faster at this than SBCL. For regular CL hacking, SBCL is
           | fine.
        
         | pamoroso wrote:
         | Thanks. Although it's not ANSI Common Lisp compliant and misses
         | modern niceties, I love a Lisp Machine environment like Medley
         | because it's a self-contained, self-sufficient, coherent
         | computing universe. A rich space for my personal projects and
         | explorations.
        
         | wglb wrote:
         | I agree. I took a very brief look at Medley and SBCL+emacs is
         | working well for me.
        
       | commandlinefan wrote:
       | I love picking up old technical books. They always seem to have a
       | perspective that's lacking in more recent books (not that recent
       | books don't _also_ have a useful perspective, just a different
       | one). The sort of information the author assumes, or doesn't
       | assume, conveys as much as the topic of the book itself.
       | 
       | I've been trying to work through The Little Schemer myself lately
       | in the same vein as the poster. It's tough going, honestly, but
       | so far I think it's been worth it.
        
         | bombcar wrote:
         | My guilty pleasure has been going through the books at thrift
         | stores and reading the old technical manuals/guides/etc.
         | 
         | The biggest thing with the older ones is the lack of assumption
         | of Internet, so the book will refer to itself instead of
         | referring to online/other documentation.
         | 
         | I've learned things reading a "Missing Manual" for an operating
         | system that is 15 years out of date that still work today.
        
           | Qem wrote:
           | > The biggest thing with the older ones is the lack of
           | assumption of Internet, so the book will refer to itself
           | instead of referring to online/other documentation.
           | 
           | I also miss this aspect of old books. They were more self-
           | contained.
        
             | bombcar wrote:
             | I feel I can get a quite detailed understanding of DOS,
             | Windows 3.11, even Windows 95 from printed books and
             | reference materials of the time.
             | 
             | But I feel much of the documentation/reference around
             | things from 2010 is both late enough that it wasn't
             | printed, and old enough that what was online has failed or
             | faded away.
        
               | asciimov wrote:
               | It helps that stable versions lasted a lot longer back
               | then, and things moved around a lot less.
               | 
               | Nowadays if you need to change a setting, you not only
               | have to contend with major version but what biannual
               | revision of windows you are using, and hope that someone
               | at Microsoft hasn't decided to move that option in an
               | update.
        
               | commandlinefan wrote:
               | Even the documentation that is still around is
               | disorganized and useless. I want a linear path I can
               | follow to learn something, not some random collection of
               | hyperlinks to more information.
        
             | tzs wrote:
             | Another great thing about books is that they have a built
             | in order to them. Start at page 1 and read the pages in
             | order until the end. You can skip around, and they provide
             | some aid for that via the table of contents and the index
             | and references in the text to other parts of the text, but
             | they (usually) are designed so that if you just start at
             | the beginning and read through to the end you get
             | everything the book has to offer in an order that makes
             | sense.
             | 
             | Compare to far too much online documentation. This is what
             | I've frequently run into. I want to learn about some
             | particular subject, so I find a site that has an
             | introduction or tutorial on that. The document is even
             | book-like in the sense that it is organized as pages, and
             | at the bottom of each page there are "next" and "previous"
             | links.
             | 
             | But there is a sidebar on each page, with lists of related
             | material and some of that sounds like material I'm going to
             | need to know at some point. But there is no indication if
             | it is material that I'm going to come across later if I
             | just keep following the "next" links or it is something
             | beyond the scope of the current document that I'll need to
             | bookmark now to come back to later.
        
               | commandlinefan wrote:
               | > and read the pages in order until the end
               | 
               | And if you put it down and pick it back up tomorrow, it
               | will still be where you left off.
        
         | munificent wrote:
         | I love old books too. One I really really enjoyed is Wirth's
         | "Algorithms + Data Structures = Programs". It's so well
         | written, and beautifully typeset and bound. Just a lovely
         | little artifact.
        
         | myth_drannon wrote:
         | And I thought I was the only one with this hobby of buying and
         | reading old computer programming books.
        
           | commandlinefan wrote:
           | I must admit, though, that I buy _way_ more of them than I
           | read.
        
           | wglb wrote:
           | I went on an archaeological tear a few years ago and acquired
           | every book that I could find that talked about higher-level
           | assembly language programming, such as XPL/S, "A systems
           | Implementation Language for the Xerox Sigma Computers" and
           | "Machine Oriented Higher Level Languages".
        
         | adamc wrote:
         | The Little Schemer is one of my favorite books. Really
         | delightful.
        
         | bsder wrote:
         | "The Little Schemer" is one of the best programming books
         | _ever_.
         | 
         | I have used it to teach scheme/lisp to people who would never
         | learn "programming". It's just that good.
        
         | pcblues wrote:
         | I think some technical writers in the past could also write
         | novels (i.e. Jerry Pournelle) and the transfer of ability and
         | knowledge would go both ways. So when you read a manual like
         | the one for the Jupiter Ace, it is an absolute joy.
         | 
         | "You may well be wondering by this stage why the computer isn't
         | taking any notice of all this rubbish you've typed in. The
         | reason is not that it's already noticed it's rubbish, but
         | simply that it hasn't looked yet. It won't take any notice
         | until you press what is just about the most important key on
         | the keyboard, the one marked ENTER (on the right-hand side, one
         | row up)."
         | 
         | http://jupiter-ace.co.uk/downloads/JA-Manual-First-Edition-[...
        
         | busfahrer wrote:
         | One of my favourites is "The AWK Programming Language". Very
         | concisely written, very natural to follow, and very insightful
         | even if you do not care about AWK at all.
         | 
         | https://awk.dev/
        
           | asa400 wrote:
           | I second this. Awk is one of my favorite programming
           | languages (I would even say, one of my favorite tools) and
           | "The AWK Programming Language" is definitely a worthy book.
        
             | anthk wrote:
             | You both will like this gopher hole:
             | 
             | gopher://hoi.st
             | 
             | It has virtual machines implemented in awk, some generic
             | awk library and a good 'phlog' to read great posts on unix,
             | minimalism and several tools and games. The freecell game
             | it's a easy example.
        
         | giancarlostoro wrote:
         | I will likely never be paid to work with it, nor will I ever
         | justify working with it, but I still have a VB6 'COMPLETE' book
         | I am probably never throwing away, it is a lot of fun to go
         | through it over the years and look back at how things used to
         | be. I wish VB had retained its ability to build native apps
         | instead of becoming a .NET language that eventually got
         | dropped. It was a fun first-time programming language, and I'm
         | sure there's plenty of apps still coded in VB6 out in
         | production right now...
        
       | jes wrote:
       | I have had this book for many years. I remember the toy expert
       | system ("Otto") and enjoyed learning from the example. If I
       | remember correctly the author makes good use of CLOS in the book.
        
         | pamoroso wrote:
         | Tatar's book doesn't cover or mention CLOS.
        
           | jes wrote:
           | Yeah, I was thinking of a different book. Here's the link to
           | the other one:
           | 
           | https://www.amazon.com/Object-Oriented-Programming-COMMON-
           | LI...
        
       | anthk wrote:
       | Read "Common Lisp: A Gentle Introduction to Symbolic Computation"
       | , much better. And funnier. Later, "Paradigms of Artificial
       | Intelligence Programming".
        
       | cyrialize wrote:
       | I used to have a bunch of old technical books. My university's
       | library would give them away, and I'd pick them up for novelty.
       | 
       | I ended up getting rid of most of them a while back, since I
       | carried them with me from move to move. Now that I'm in a place
       | that I'll be in for a long time, I really miss these books.
       | 
       | The coolest one I saw, but never picked up, was a book entirely
       | dedicated to creating a chess engine. It was published years ago
       | at the time I saw it (seen 2013, published in 1980?), but I doubt
       | the basics have changed all that much.
       | 
       | The art and graphics in old books are great as well. I always
       | like to think of older albums, books, and movies as a snapshot in
       | time. It's fun to see a snapshot in time in such a specific
       | niche.
        
       ___________________________________________________________________
       (page generated 2024-02-22 23:01 UTC)