Search code examples
schemelispracket

Returning from a function inside when statement


All I'm trying to do is use a when statement to return a value :( I want the functionality of:

if(x)
    return y

And I'm trying to use:

(when (x) y)

But the when statement is not evaluating in a way that exits the function and return y. It just happily carries on to the next line. Is there a way to do this without making an extremely ugly looking if-else block? mzscheme/racket does not allow 1-armed ifs.


Solution

  • You tagged this as both Common Lisp and Racket, which are two completely different languages. If you're using Racket or Scheme and want to return from a function early, you can do it using a continuation:

    (define (my-function x y)
      (call-with-current-continuation
        (lambda (return)
          (when x (return y))
          ;; Rest of code not evaluated if X is true
          )))
    

    In some Scheme implementations including Racket, call-with-current-continuation is also bound to call/cc, but call-with-current-continuation is the only portable way to use continuations.

    The above is even uglier than using a cond statement. If you want to get rid of all that extra crap, you can define a macro that creates an alternate version of define that automatically creates the continuation and binds it to return:

    (define-syntax define/return
       (syntax-rules ()
          ((_ (name . args) . body)
           (define (name . args)
             (capture-vars (return)
                (call/cc (lambda (return) . body)))))))
    

    This requires you to have my capture-vars macro, which you can find in this answer.

    EDIT: Leppie provided the following implementation of define/return which is much simpler since it doesn't require my capture-vars macro:

    (define-syntax define/return
      (lambda (x)
        (syntax-case x ()
          [(_ (name . args) . body)
            (with-syntax
              ([return (datum->syntax #'name 'return)])
             #'(define (name . args)
                 (call/cc (lambda (return) . body))))])))
    

    EDIT 2: However, it's easy to accidentally un-capture the definition of return doing it this way, if you incorporate a define/return in another macro.

    Then return will behave as you'd expect and not be syntactically repugnant:

    (define/return (my-function x y)
        (when x (return y))
        ;;; more code...
    )
    

    However, if you're using Common Lisp, the situation is different. In Common Lisp, (return y) will only compile when a block named nil is defined. Certain forms implicitly define a block named nil, such as the loop macro. Without a block named nil, you can still use return-from to return from a named block. If you're in a function defined with defun, the name of that function is also the name of a block that wraps that function, so this would work:

    (defun my-function (x y)
       (when x (return-from my-function y))
       ;;; more code
       )