2

Watching this video (11:56)

It shows a recursive procedure that multiplies the numbers contained in a list

The idea is that if the list contains a zero, the whole stack of recursive calls can be discarded and 0 can be returned

So to save some multiplications

It does so by early exiting the procedure with delimited continuations

I'd like to reproduce this in Guile Scheme and I wrote this code

(define (times numbers)
  (define (times-iter numbers)
    (match numbers
      ((a-number rest ...) 
         (* a-number (times rest)))
      ('() 1)
      ((0 rest ...) (shift k 0) )
      ))
  (reset (times-iter numbers))
    )

it multiplies correctly but if I pass it a list containing a zero and I trace such call, I get

scheme@(guile-user)> ,trace (times2 '(1 3 0 4))
trace: |  (times2 (1 3 0 4))
trace: |  |  (default-prompt-tag@@ice-9/control)
trace: |  |  (_)
trace: |  |  ("prompt")
trace: |  |  (_)
trace: |  |  |  (list? (3 0 4))
trace: |  |  |  #t
trace: |  |  |  (times (3 0 4))
trace: |  |  |  |  (list? (0 4))
trace: |  |  |  |  #t
trace: |  |  |  |  (times (0 4))
trace: |  |  |  |  |  (list? (4))
trace: |  |  |  |  |  #t
trace: |  |  |  |  |  (times (4))
trace: |  |  |  |  |  |  (list? ())
trace: |  |  |  |  |  |  #t
trace: |  |  |  |  |  |  (times ())
trace: |  |  |  |  |  |  1
trace: |  |  |  |  |  4
trace: |  |  |  |  0
trace: |  |  |  0
trace: |  |  0
trace: |  (_ #<procedure values _> (0))
trace: |  (_ 0)
trace: |  0
scheme@(guile-user)> 

It seems to me that the early exit doesn't kick in and the whole stack of multiplications gets applied

What am I doing wrong ?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
user1632812
  • 431
  • 3
  • 16
  • 1
    The video is almost an hour long. Could you narrow down to the part that shows how to do this? – Barmar Aug 18 '22 at 20:23
  • 2
    Probably the 0 matches the `a-number` case and it never gets to the literal 0 case – Shawn Aug 18 '22 at 20:23
  • 1
    Exactly. In the example in the video, the test for `0` is first. – Barmar Aug 18 '22 at 20:26
  • @Bamar sorry, it's 11:56 – user1632812 Aug 18 '22 at 20:47
  • @Shawn you're right Reordering the clauses in the match fixed it – user1632812 Aug 18 '22 at 21:36
  • 1
    Note that `times-iter` is not in fact iterative, and that in a linear process like this you don't need to escape that way: you can just stop iterating. – ignis volens Aug 19 '22 at 07:53
  • 1
    @ignisvolensyou can stop iterating but the multiplications up the the 0 will be carried out anyway In discarding the continuation, you discard those too So I understand, at least – user1632812 Aug 19 '22 at 09:29
  • @ignisvolensand you're right, times-iter should have been caled times-recur – user1632812 Aug 19 '22 at 09:30
  • 1
    @user1632812 yes, of course you are right! I had thought 'oh, doing it this way is rubbish, much better to iterate and multiply as you go, since multiplication is cheap', but by using the recursive version (which, in real life, would be rubbish for this but is a good example) you do indeed never do all those multiplications which makes the point. Sorry! – ignis volens Aug 19 '22 at 10:38
  • 1
    @user1632812 You are recursing with `times` and not the ill-named`times-iter`. Thus in each iteration you delimit with `reset`. Also you need to do the default case as the last match and not the first since a list of two or more elements with the first and element zero will match the first term and never actually use `shift` – Sylwester Aug 27 '22 at 13:11
  • @Sylwester you're right, I was wrongly calling times, rather than times-iter I don't know why I had the impression that it was working you're also right about the order of clauses – user1632812 Aug 27 '22 at 14:04

1 Answers1

3

Based on the comments, it's unclear if you still have a question here. (shift k 0) does indeed clear the current continuation and discards it.

I don't have Guile on this machine but I wrote a simplified times example below with racket using cond -

(require racket/control)

(define/traced (times numbers)
  (cond ((null? numbers) 1)
        ((zero? (car numbers)) (shift k 0)) ;; <- shift
        (else (* (car numbers) (times (cdr numbers))))))

@ignisvolens provides define/traced in this Q&A. reset wraps the expression but you could move it to an inner auxiliary function like you did in your code -

(reset                           ;; <- reset
 (times '(1 2 3 4 5 6 7 8 9)))
(times (1 2 3 4 5 6 7 8 9)) ...
 (times (2 3 4 5 6 7 8 9)) ...
  (times (3 4 5 6 7 8 9)) ...
   (times (4 5 6 7 8 9)) ...
    (times (5 6 7 8 9)) ...
     (times (6 7 8 9)) ...
      (times (7 8 9)) ...
       (times (8 9)) ...
        (times (9)) ...
         (times ()) ...
         -> (1)
        -> (9)
       -> (72)
      -> (504)
     -> (3024)
    -> (15120)
   -> (60480)
  -> (181440)
 -> (362880)
-> (362880)
362880

We can see the immediate exit when the 0 is encountered -

(reset (times '(1 2 0 4 5 6 7 8 9)))
(times (1 2 0 4 5 6 7 8 9)) ...
 (times (2 0 4 5 6 7 8 9)) ...
  (times (0 4 5 6 7 8 9)) ...
0

Notice in the first example, define/traced shows -> ... when times returns a value. In the second example, the entire continuation is discarded and times never fully evaluates.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    sorry for being unclear about wether my question is answered or not. It is, thanks to the second comment (the one by @Shawn about the order of the clauses in the match form I failed to see it as a solution on the spot because in reloading a file too many times I had messed up my namespace When I started from scratch it worked like a cham, correctly discarding the calculation – user1632812 Aug 25 '22 at 15:55