Philip,

On Fri, Sep 13, 2019 at 11:43:06AM +0200, Philip K. wrote:
> 
> Hi,
> 
> I was reading a thread on an imageboard[0] the other day, then I came
> across a most peculiar "bug", if it even is one. Since the original
> example was a bit dense (it tried to solve a problem someone else had
> posted, that's not relevant here), I tried to construct a minimal
> working example to discuss here.
> 
> Compare
> 
>     (define (reverse-iota-1 max)
>       (let ((numbers '(start)))
>         (let loop ((val 0))
>           (append! numbers
>                    (if (< max val)
>                        '()
>                        (begin
>                          (loop (1+ val))
>                          (list val)))))
>         numbers))
> 
> and
> 
>     (define (reverse-iota-2 max)
>       (let ((numbers '(start)))
>         (let loop ((val 0))
>           (append! numbers
>                    (if (< max val)
>                        '()
>                        (begin
>                          (loop (1+ val))
>                          (list val)))))
>         (cdr numbers)))
> 
> (I know, the style is horrible, but that's not the point. Also, both
> have an internal state, so you have to re-eval the function every time
> before starting the function itself.)
> 
> The only difference is in the last line. The first function returns the
> entire list (with the start symbol), and the second tries to chop it
> off.
> 
> But what happens is that (reverse-iota-1 4) evals to '(start 3 2 1 0)
> while (reverse-iota-2 4) just returns '()!
> 
> This seems weird, since my intuition, and that of the poster above, was
> that all that should change in reverse-iota-2 is that the "start" symbol
> should fall away.
> 
> It's obvious that this has something to do with the destructive
> "append!", but I'm not quite sure what leads to this unexpected result.
> Is it maybe a optimisation error? Any opinions?


Well, if i understand the issue correctly, there are two issues :-)

1) The quotation from the Guile manual:

     ‘append’ doesn’t modify the given lists, but the return may share
     structure with the final OBJ.  ‘append!’ is permitted, but not
     required, to modify the given lists to form its return.


2) 'let' itself should return the last evaluated expression
(right?). The external 'let' does just so, and the internal 'let'
is irrelevant (apart from the issue with permission to 'append!',
shown above, to modify the value of its arguments) .

So, if you would use just 'append' instead of 'append!', you would
get just the value of 'numbers' (just '(start)) in the first case,
and the value of its 'cdr' in the second case (obviously, '()
:-)).  However, since 'append!' is used instead, it is
unpredictable for me (the behaviour of 'append!' is not
standardized), if it will change the value of 'numbers' itself, so
there are two cases you've got.

Well, dunno, which conditions force the Guile optimizer to choose
one of the strategies.  Seems, i would prefer internal 'let's in
both cases are thrown out.

-- 
  Vladimir

(λ)επτόν EDA — https://github.com/lepton-eda

Reply via email to