`let` expressions

Scheme style

(let ( (a exprA)
       (b exprB) )
   (expr using a and b) )

Any thoughts on the best way to do this today, and any plans to more directly support this?

With school math on my mind, I want to maximize working with expressions & values in the workspace (scripting area). I don't want to encourage the make variable and set variable style. And I know macros might someday be the answer :~) but debugging the expanded lambda forms could be daunting to students even then, as semantically meaningful names a and b are moved around.

This is current let custom block
image

Note var is defined as an upvar

Thanks. The scope of var worries me, and it being a command block.

In block terms seems we want a reporter version of a C-style block, with an expandable list of pairs (name expr), and a single contained expression.

What we want and what's available can be two different things :slight_smile:

There was a thread

To avoid scope problem, I tested something slightly similar, once, based on block bound local variables abuse.
untitled script pic (12)
untitled script pic (14)
a,b are global hidden shared variables getters/setters.

in the let primitive thread, you had a link to a project, and one of the blocks is a list of upvars, how did you do that? and also why will my script not recognise them
pic:
untitled script pic (2)
result, only shows variable, maybe because of error?:
untitled script pic (1)
link:Snap! 7.0.0 - Build Your Own Blocks

Running

pic:

gives you the error in

result, only shows variable, maybe because of error?:

right?

This is my test:
Screenshot (241)

It's a leftover of the experiments.
Variadic upvars do not create variables, so I was looking at JS level.

Fully functional are other blocks. Variable value reporter with side effect.
untitled script pic - 2021-12-14T221252.600
untitled script pic - 2021-12-14T221303.837
untitled script pic - 2021-12-14T221701.919

C-style doesn't make sense for reporters; it's based on the top-to-bottom style of imperative programming. Compare the two IF/THEN/ELSE primitives, one for commands and one for reporters. I mean, it's not disallowed, just a bit weird.

If it'll make you happier, you can
untitled script pic (2)

untitled script pic (3)

But, much as I'd love to write SICP in Snap!, it's not our main purpose, which is, you know, to attract kids to learn a little CS because they see that it helps them make cool projects.

Yes, I understand that.

I'm fundamentally missing a scoped block, which I think is important. We have such scoping in function definitions, we have script and global variables (don't yet grok up-vars but they seem right now like function outputs), but no scoping within an arbitrary block.

And if the answer is macros, so be it. I'd try and pitch in but I'm not sure I can ramp up quickly enough.

I'm not sure what you mean. Blocks follow lexical scope. Oh, do you mean you want internal definitions, to make local named blocks? We want that too, but there's a lot to think about first in the design of the user interface.

Presumably you'd make an internal definition by clicking "make a block" (or any of the various alternative notations) in an open block editor. But then what? Does a new editor open? Does a new hat block appear inside the original editor? Can you make named blocks inside a named block inside a toplevel block? How does the user see the scope of the definition? How does the user use the block? Is there a local palette inside the block editor? (In the original BYOB, block editors did have palettes inside, for what are now called script variables.)

We are in fact talking about these questions; I had a conversation with Jens on the subject just yesterday. But no solutions yet.

(Yes, proposals on this thread are okay, from people who are sure they understand what "lexical scope" and "internal definition" mean. A proposal has to at least answer the questions above. Don't make a competition out of this -- propose things if you really have something to contribute, and make sure anything you propose will be visually self-explanatory to users.)

I understand and agree. But still have 2 less profound visual questions:

  1. I meant a C-style reporter with a place for a single other reporter. Assume common case of many let bindings inside one let form, and visually highlight the scope and the different roles of the bindings and the contained expression.
    image

  2. Perhaps related, is there a way to manipulate the aspect ratio of a block and how its placeholders are placed (in general, but specially for repeating inputs) like you seem to have here:
    image

Expanding on @warped_wart_wars's somewhat terse answer, what you can manipulate isn't actually the aspect ratio, but rather just places with forced line breaks. In this case, that happened to do just what you want.

About your other question, we do want to invent input groups, so we can have variadic input groups, which is what you want here. But that'll take some effort both to design and to implement, and meanwhile, my version will give you the semantics you want (well, okay, let* rather than let) and almost the syntax you want, and I don't think getting the other 5% of what you want is our most urgent task.

I agree. Thanks!

I'm not sure of the difference between those.

(let* ((a 5) (b (+ a 3))) b)
sets a to 5, then sets b to 8 (5+3), then reports 8.

(let ((a 5) (b (+ a 3))) b)
simultaneously sets a to 5 and b to three more than whatever value a has in the enclosing environment, or gives an error if a has no binding in the outer environment, then reports whatever b turns out to be.

Ah. So let does it in parallel, but let* does it in order.

LET is syntactically transformed into a single lambda and invocation, so
(let ((a 5) (b (+ a 3))) b)
turns into

( (LAMBDA (A B) B)  5 (+ a 3))
  ________________  _ _______

which is an expression with three subexpressions: the lambda expression and two input expressions. All three are evaluated independently (in principle, in parallel, and likely in practice sooner rather than later with multicore computer chips), and then the function from the lambda expression is applied to the input values.

LET* is syntactically transformed into nested LET expressions, so
(let* ((a 5) (b (+ a 3))) b)
is translated to

(let ((a 5))
  (let ((b (+ a 3)))
    b))

which is in turn transformed into two nested lambda expressions and two nested procedure calls.