medley icon indicating copy to clipboard operation
medley copied to clipboard

Missing il:PSETQ (and speculation about CL:PSETQ)

Open masinter opened this issue 3 months ago • 5 comments

In a long conversation in the LispCore group

it was noticed that there was no IL:PSETQ even though it was listed in the IRM.

@masinter also claimed that the definition of CL:PSETQ was buggy for more than 3 variables to be set in parallel.

This issue is to move the discussion to GitHub issues.

At this point I think the implementation of CL:PSETQ is fine, and will be fine if we just import CL:PSETQ into the IL package.

masinter avatar Sep 20 '25 18:09 masinter

(@pamoroso @RyanBurnside) I'm not sure about the 'environment' project, which was shorthand for 'programming environment' and intended for tools like Masterscope and BREAK and so on. But we could fix it by either defining IL:PSETQ. Given the history of PSETQ not being part of Interlisp historically, I'm tempted to say this is a "documentation" problem -- it was wrong to add (nominally IL:) PSETQ to the IRM in the section on litatoms. The implementation is right and it's just the docuemtnation.

CL:PSETQ will work in Interlisp code because (a) Interlisp compiler and interpreter has been extended to work with DEFMACRO-defined macros and (b) although CL:SETQ and IL:SETQ are different the differences are in their handling of more than 2 arguments) the difference in scoping (CL:SETQ can set lexicallly scoped variables ) is handled if i recall by special casing CL:SETQ in the IL:EVAL.

The IRM that you access with DINFO has some deficiencies mainly missing features -- e.g., you can introduce bindings of variables to bindings using LET or internal lambdas (more than PROG). Revising the DINFO for a modifiable Introduction is a different project.

masinter avatar Sep 21 '25 01:09 masinter

I've change the roject from environment to documentation.

pamoroso avatar Sep 21 '25 05:09 pamoroso

A friend and I worked through it and I've come to the conclusion that within Medley the CL:PSETQ form is correct. There is a case where the results differ for Common Lisp implementations using the LET form but it comes down to a somewhat nonsensical edge case of assigning a base value more than once in the CL:SETQ form. If used with common sense I think it's fine to keep the current form. I do wonder though if Medley is Tail Call optimized for a deeply generated form.

Credit to my brother in Lisp Pete "psilord" Keller

 (let ((A 10)
      (B 20)
      (C 30)
      (D 40))
  (psetq A (+ B C D 100)
         B (+ A C D 100)
         C (+ A B D 100)
         D (+ A B C 100))
  (list A B C D)) -> (190 180 170 160)
 
We lift out the psetq form:
  (psetq A (+ B C D 100)
         B (+ A C D 100)
         C (+ A B D 100)
         D (+ A B C 100))
 
Expanded out in IL's expander, it looks like this:
 
(progn
 (setq A 
       (prog1 (+ B C D 100)
              (setq B (prog1 (+ A C D 100)
                      (setq C (prog1 (+ A B D 100)
                              (setq D (prog1 (+ A B C 100)))))))))
 nil)
 
Now we annotate them with CL's exper markers for easier reference in the
explanation below:
 
#1=(progn
 #2=(setq A   
       #3=(prog1 #4=(+ B C D 100)
              #5=(setq B #6=(prog1 #7=(+ A C D 100)
                      #8=(setq C #9=(prog1 #10=(+ A B D 100)
                              #11=(setq D #12=(prog1 #13=(+ A B C 100)))))))))
 #14=nil)
 
Now, the analysis.
 
Fact:
  IL's definition of PROGN definition states it evaluates in order, which I
  assume to be left to right, its arguments, and then returns the value of the
  first argument.
 
Fact:
  IL's definition of PSETQ indicates the symbols are not evaluated, but the
  value forms are, and that all value forms are evaluated before the 
  assignments happen.
 
So, evaluation proceeds like this:
 
#1 evaluates left to right, so it does #2 first.
 
The definition of setq states, in #2, that A is not evaluated and the the value
of #3 is evaluated and then assigned to A--but there is a bit of computation
that has to happen before the assignment to A can happen! 
 
So, how do we evaluate #3?  By evaluating #4 first. #4 looks up B C D without
any changes, which is as expected. 
 
Then, #5 is evaluated, which requires the evaluation of #6. 
 
Evaluating #6 looks up the A C D values without any changes, which is as
expected. 
 
Then #8 must be evaluated, which causes #9 to be evaluated, and #10 looks up A
B D without changes, which is as expected. 
 
Then #11 must be evaluated which causes #12 to be evaluated, then #13, which
looks up A B C to without changes, as expected.
 
At this point, all of the value forms using A B C D have been evaluated with no
changes to A B C or D and we fall out of the recursion.
 
Then #13 returns its first (and only value), which becomes #12 and
is assigned to D.
 
Then #11 returns its value, which is thrown out by #9, and #9 returns #10
instead which is assigned to C.
 
Then #8 returns its value, which is thrown out by #6, and #6 returns #7
instead and assigned to B.
 
Then #5 return its value, which is thrown out by #3, and #3 return #4
instead and assigned to A.
 
#2 returns to #1.  #1 then throws out the value #2 and returns #14.
 
Hence, it appears that this expansion is actually correct.

RyanBurnside avatar Sep 22 '25 02:09 RyanBurnside

Just to circle back on this issue, is the plan then to remove it from the IRM manual? I'd be fine with this. I also recall there was some mention of documentation not being "editable".

I do recall @masinter mentioning that there were some "subtle" issues with the current CL:PSETQ implementation. I'm not sure if that means it merits a look-see or not to be fixed.

RyanBurnside avatar Sep 28 '25 04:09 RyanBurnside

(psetq x 10 x 20 x 30) x

currently gives 10 not 30. The hyperspec doesn't say. sbcl says 30. With (psetq a b c d e f) the order is

compute b d f set-a set-c set-f

to fix we'd need to reverse the order of assignments. While CL:PSETQ is elegant,

ED(CL:PSETF IL:FUNCTIONS)

might be less messy to modify.

masinter avatar Sep 28 '25 23:09 masinter