views:

150

answers:

4

I'm trying to find out how I can do an "early return" in a scheme procedure without using a top-level if or cond like construct.

(define (win b)
 (let* ((test (first (first b)))
        (result (every (lambda (i) (= (list-ref (list-ref b i) i) test))
                       (enumerate (length b)))))
  (when (and (not (= test 0)) result) test))
 0)

For example, in the code above, I want win to return test if the when condition is met, otherwise return 0. However, what happens is that the procedure will always return 0, regardless of the result of the when condition.

The reason I am structuring my code this way is because in this procedure I need to do numerous complex checks (multiple blocks similar to the let* in the example) and putting everything in a big cond would be very unwieldy.

+1  A: 

One way would be to use recursion instead of looping, then an early exit is achieved by not recursing further.

cobbal
A: 

You can use the "call with current continuation" support to simulate a return. There's an example on wikipedia. The function is called call-with-current-continuation, although there's often an alias called call/cc which is exactly the same thing. There's also a slightly cleaner example here

Note: This is quite an advanced Scheme programming technique and can be a bit mind bending at first...!!!!

Sean
+4  A: 

Here is how to use call/cc to build return yourself.

(define (example x)
  (call/cc (lambda (return)
    (when (< x 0) (return #f))
    ; more code, including possible more calls to return
    0)))

Some Schemes define a macro called let/cc that lets you drop some of the noise of the lambda:

(define (example x)
  (let/cc return
    (when (< x 0) (return #f))
    0))

Of course if your Scheme doesn't, let/cc is trivial to write.


This works because call/cc saves the point at which it was called as a continuation. It passes that continuation to its function argument. When the function calls that continuation, Scheme abandons whatever call stack it had built up so far and continues from the end of the call/cc call. Of course if the function never calls the continuation, then it just returns normally.

Continuations don't get truly mind-bending until you start returning them from that function, or maybe storing them in a global data structure and calling them later. Otherwise, they're just like any other language's structured-goto statements (while/for/break/return/continue/exceptions/conditions).


I don't know what your complete code looks like, but it might be better to go with the cond and to factor out the complex checks into separate functions. Needing return and let* is usually a symptom of overly imperative code. However, the call/cc method should get your code working for now.

Nathan Sanders
Thanks for the explanation. I ended up using a top-level cond along with creating separate functions as you suggested.
Suan
A: 

In this case you don't want a when, you want an if, albeit not top-level.

(define (win b)
  (let* ((test (first (first b)))
         (result (every (lambda (i) (= (list-ref (list-ref b i) i) test))
                        (enumerate (length b)))))
    (if (and (not (= test 0)) result) 
        test
        0)))

The reason it was always returning zero is that whether or not the body of the when got executed, its result would be dropped on the floor. You see, the lambda implicit in the function define form creates an implicit begin block too, so

(define foo 
  (lambda (b)
     (begin
       (let ...)
       0)))

and the way begin works is that it returns the result of the last form inside, while dropping all the intermediate results on the floor. Those intermediate results are intended to have side effects. You're not using any of that, which is great(!), but you have to be careful to only have one form (whose result you really want) inside the function definition.

Grem

Gregory Marton
Yes. In Scheme, if you have multiple expressions in a (lambda () stmt1 stmt2 stmt3), only the result of the last statement is returned. In the asker's example, continuations are probably overkill when having an if statement at the end would work.
erjiang