Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

The lisp is harder to read, for me. The first double paren is confusing.

    (let (bar-x (bar x))
         (quux-y (quux y)))
    (foo bar-x quux-y z)
Why is the second set of parens necessary?

The nesting makes sense to an interpreter, I'm sure, but it doesn't make sense to me.

Is each top-level set of parens a 'statement' that executes? Or does everything have to be embedded in a single list?

This is all semantics, but for my python-addled brain these are the things I get stuck on.



The let construct in Common Lisp and Scheme supports imperative programming, meaning that you have this:

  (let variable-bindings statment1 statement2 ... statementN)
If statementN is reached and evaluates to completion, then its value(s) will be the result value(s) of let.

The variable-bindings occupy one argument position in let. This argument position has to be a list, so we can have multiple variables:

  (let (...) ...)
Within the list we have about two design choices: just interleave the variables and their initializing expressions:

  (let (var1 value1
        var2 value2
        var3 value3)
    ...)

Or pair them together:

  (let ((var1 value1)
        (var2 value2)
        (var3 value3)
    ...)
There is some value in pairing them together in that if something is missing, you know what. Like where is the error here?

  (let (a b c d e) ...)
we can't tell at a glance which variable is missing its initializer.

Another aspect to this is that Common Lisp allows a variable binding to be expressed in three ways:

  var
  (var)
  (var init-form)
For instance

  (let (i j k (l) (m 9)) ...)
binds i, j and k to an initial value of nil, and m to 9.

Interleaved vars and initforms would make initforms mandatory. Which is not a bad thing.

Now suppose we have a form of let which evaluates only one expression (let variable-bindings expr), which is mandatory. Then there is no ambiguity; we know that the last item is the expr, and everything before that is variables. We can contemplate the following syntax:

  (let a 2 b 3 (+ a b)) -> 5
This is doable with a macro. If you would prefer to write your Lisp code like this, you can have that today and never look back. (Just don't call it let; pick another name like le!)

If I have to work with your code, I will grok that instantly and not have any problems.

In the wild, I've seen a let1 macro which binds one variable:

  (let1 var init-form statement1 statement2 ... statementn)


I am not a Lisp expert by any stretch, but let's clarify a few things:

1. Just for the sake of other readers, we agree that the code you quoted does not compile, right?

2. `let` is analogous to a scope in other languages (an extra set of {} in C), I like using it to keep my variables in the local scope.

3. `let` is structured much like other function calls. Here the first argument is a list of assignments, hence the first double parenthesis (you can declare without assigning,in which case the double parenthesis disappears since it's a list of variables, or `(variable value)` pairs).

4. The rest of the `let` arguments can be seen as the body of the scope, you can put any number of statements there. Usually these are function calls, so (func args) and it is parenthesis time again.

I get that the parenthesis can get confusing, especially at first. One adjusts quickly though, using proper indentation helps.

I mostly know lisp trough guix, and... SKILL, which is a proprietary derivative from Cadence, they added a few things like inline math, SI suffixes (I like that one), and... C "calling convention", which I just find weird: the compiler interprets foo(something) as (foo something). As I understand it, this just moves the opening parenthesis before the preceding word prior to evaluation, if there is no space before it.

I don't particularly like it, as that messes with my C instincts, respectively when it comes to spotting the scope. I find the syntax more convoluted with it, so harder to parse (not everything is a function, so parenthesis placement becomes arbitrary):

    let( (bar-x(bar(x))
         quux-y(quux(y)))
    foo(bar-x quux-y z)
    )


> Why is the second set of parens necessary?

it distinguishes the bindings from the body.

strictly speaking there's a more direct translation using `setq` which is more analogous to variable assignment in C/Python than the `let` binding, but `let` is idiomatic in lisps and closures in C/Python aren't really distinguished from functions.


You’re right!

    (let (bar-x quux-y)
      (setq bar-x (bar-x)
            quux-y (quux y))
      (foo bar-x quux-y z))
I just wouldn’t normally write it that way.


The code is written the same way it is logically structured. `let` takes 1+ arguments: a set of symbol bindings to values, and 0 or more additional statements which can use those symbols. In the example you are replying to, `bar-x` and `quux-y` are symbols whose values are set to the result of `(bar x)` and `(quux y)`. After the binding statement, additional statements can follow. If the bindings aren't kept together in a `[]` or `()` you can't tell them apart from the code within the `let`.


I prefer that to this (valid) C++ syntax:

  [](){}


You'd never actually write that, though. An empty lambda would be more concisely written as []{}, although even that is a rare case in real world code.


This reminds me of the terror that is the underbelly of JS.

https://jsfuck.com/




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: