Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTDP Stepper does not generate fresh names for local defs #1936

Closed
klauso opened this issue Jan 25, 2018 · 27 comments
Closed

HTDP Stepper does not generate fresh names for local defs #1936

klauso opened this issue Jan 25, 2018 · 27 comments
Assignees

Comments

@klauso
Copy link

klauso commented Jan 25, 2018

Consider this program in ISL+

(define y_0 77)
(define f (lambda (x)
            (+ 2
               (local
                 [(define y 88)]
                 (* y_0 2)))))
(f 2)

When reducing this program in the stepper there is a confusing name clash with two definitions of y_0 in the program, and the y_0 in the expression (* y_0 2) is substituted by 77 and not 88 (which is correct but contradicts the program that gets generated during reduction).

Here is an image of the decisive reduction step.
https://cdn.pbrd.co/images/H4zbeg2.png

@jbclements jbclements self-assigned this Jan 25, 2018
@jbclements
Copy link
Collaborator

Yep, sigh... my bug. Known.

@klauso
Copy link
Author

klauso commented Jan 25, 2018

Maybe this is not the right place for discussing this, but wouldn't it be possible to define the meaning of local expressions in a different way - one that does not involve the synthesis of new top-level definitions?

I've taught several big courses using HTDP, and I like the stepper a lot in general, but I really hate the treatment of local expressions. It is just too complicated for most students, and it gives wrong intuitions about what actually happens (for instance, students are worried about performance problems due to synthesizing new top-level expressions all the time).

I'd much prefer if ISL would either feature only non-recursive let (which could be dealt with by ordinary substitution) instead of local, or a less syntactic presentation of the reduction rules using environments and closures.

@jbclements
Copy link
Collaborator

Are you thinking here about a change to the stepper, or to the ISL language? I see a bit of both.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 25, 2018 via email

@klauso
Copy link
Author

klauso commented Jan 25, 2018

Matthias, thanks for the explanation. I fully understand your points, and I agree with everything (except 3b - you think the situation I described above is correct?). I'm merely reporting my experience in teaching a sizeable number of courses using HTDP - right now a course with over 600 students. You can of course argue that maybe I'm teaching it the wrong way, but my experience is that 90% of my students don't understand what the stepper does with local expressions.

I do appreciate that local definitions are so similar to top-level definitions, as you point out, but I'm asking you to also consider the downsides. In addition to the issues I describe above, there's also the issue of local define-struct, which further complicates the semantics (different invocations yield distinct struct instances) without a particularly convincing case why they are needed (apart from uniformity with top-level definitions).

(By the way, the stepper also does not seem to support local structs. For instance, this example crashes the stepper:

(define (f x)
  (local [(define-struct a (b c))]
    (list make-a a?)))

Another small gripe I have with local:
(local [(define (f x) ...)] f) (with f non-recursive) behaves very different from (lambda (x) ...) in the stepper and it is not at all obvious to students that the semantics is the same.

Having let instead of local would be bad from the perspective of uniformity with top-level and slightly decrease expressiveness (because recursive functions would need to be top-level), but it would make it significantly simpler for students to understand what is going on. Knowing Matthias, I guess my chances of convincing him to consider a change to the language and/or stepper are slim, but it's worth a try ;)

@jbclements
Copy link
Collaborator

Just to pick up on one point: local define-structs are a known bug. Mea culpa, and on the list.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 25, 2018 via email

@samth
Copy link
Sponsor Member

samth commented Jan 25, 2018

I will agree with @klauso that I also find the stepper less useful in ISL (especially ISL+lambda) than in BSL. One aspect of this is the additional reduction to add a lambda expression in every application, which is basically never what I want. A second aspect is that the combination of existing definitions plus new definitions created by the stepper is confusing. Since I regularly use the recursive aspect of local, I wouldn't want that to go away. But instead, here's a few suggestions for how it could be presented differently.

  1. Bindings that are no longer needed could be removed.
  2. Lifted bindings could be colored differently, or otherwise indicated.
  3. local expressions could be kept during reduction of their bodies, just like the entire definitions window contents is kept around during reduction.

@klauso
Copy link
Author

klauso commented Jan 25, 2018

Wouldn't there also be a syntactic way of explaining local in the stepper without the synthesis of new top-level definitions, namely by treating expressions like (local [(define x v1)..] v) as values and then having a hierarchical lookup instead of the current top-level lookup of names? I'm sure Matthias wrote some paper explaining letrec in a purely syntactic way using evaluation contexts in a clever way ;)

edit: I think I'm merely proposing the same thing as item 3. in @samth 's list.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 25, 2018 via email

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 25, 2018 via email

@klauso
Copy link
Author

klauso commented Jan 25, 2018

I'm not sure whether my proposal is the same as @samth 's, but I was considering a semantics that would reduce

(local [(define (f x) (+ 1 (f x)))]
  (f 7))

to

(local [(define (f x) (+ 1 (f (sub1 x))))]
  (+ 1 (f (sub1 7))))

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 25, 2018 via email

@mfelleisen
Copy link
Collaborator

Let me illustrate my responses re 3b with an example:

(define (d/dx f)
   (local ((define (f-prime x) 42)) ;; this is not the numerical differentiation of f! 
       f-prime))

(define (g x) (+ x 5))

(define g-prime (d/dx g))

(map g-prime '(1 2 3 4 5))

Without the general local rule, this program cannot be explained.

Once the last expression is completed, bindings for f-prime and g-prime could be eliminated assuming no other references exist, which is the local-gc rule from our original paper and one of Sam's bullets. And yes, they f-prime could be colored differently (cool idea).

@samth
Copy link
Sponsor Member

samth commented Jan 26, 2018

My suggestion 3 is more like the \lambda_vCS calculus. ;)

I would add another value production:

v ::= ... | (local [(define x v_rhs) ....]  v)

and reduction rules that reduce in the body of (and on the RHS of) local. As @mfelleisen says, this requires a small change to the semantics of variable reference, but this change is already in the reduction rules for local (they aren't substituted until needed because they're top-level bindings).

Another way of thinking of this is as taking the metaphor in HtDP, that a local expression is like having a mini definitions window, and continuing it in the stepper. Preserve the definitions window, and keep it around in the reductions.

Note that you could of course reduce (local [(define x 1)] 2) to 2, which is like a GC rule, but that doesn't affect the semantics.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 26, 2018 via email

@klauso
Copy link
Author

klauso commented Jan 28, 2018

But couldn't you achieve the same without any top-level lifting by a rule like this:

E[(local (... defines ..) v)] -> E[v']
whereby v' is like v except that for every (define x ...) in defines every x in v is replaced by (local (... defines ...) x)

That rule would subsume both rule 2 and rule 3.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 28, 2018 via email

@jbclements
Copy link
Collaborator

What about mutation?

The existing model works great for mutation, and it looks to me like this one would not.

(Feel free to make snarky comments about whether I'll get to mutation before I die.)

@jbclements
Copy link
Collaborator

Ah! I took a bathroom break to compose my message, and Matthias beat me.

@klauso
Copy link
Author

klauso commented Jan 28, 2018

True, but I for one would be happy to have a simpler semantics for ISL. I don't use ASL and set! in my courses, and I assume many others who teach with the *SL languages don't use it either. Why not just have a semantics for ASL that is not a direct extension of the ISL semantics?

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 28, 2018 via email

@klauso
Copy link
Author

klauso commented Jan 28, 2018

Fair enough, my opinion is just one data point. However, unless I'm mistaken even your own book doesn't use mutation anymore. Also, I think it would be illuminating for students to illustrate that some rules ("equational reasoning") are destroyed by the introduction of mutation, i.e., there is a price to be paid for adding mutation. Finally, it is a little sad if the reduction rules, if read as equations, do not validate program refactorings that are in fact valid in ISL, just for compatibility with ASL.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 28, 2018 via email

@klauso
Copy link
Author

klauso commented Jan 28, 2018

I meant that the reduction rule I proposed above, read as equation, is a valid refactoring in ISL/+, but it's not easily derivable from your reduction rules 1-3.

@mfelleisen
Copy link
Collaborator

mfelleisen commented Jan 28, 2018 via email

@jbclements
Copy link
Collaborator

jbclements commented Jul 9, 2019

Closed by racket/htdp@d7620a37ed7864fa8c18fb151.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants