Returning from a function inside when statement
A simple way in Racket:
(define (foo x)
(let/ec return
(when (= x 0)
(return 'zero))
'not-zero))
Here ec stands for escape continuations, which are cheaper than full continuations.
Okay,I'm going to be "that guy"; there's a good reason for using the "extremely ugly" solution of putting the rest of the function inside of the "else" of your conditional; it makes the code easier to read and understand. When I'm trying to understand what a function does, I don't want to have to scan through all the code hunting for hidden return
s and strange control flow. A straightforward 'if' or 'cond' makes it very clear under what circumstances each piece of code will be used.
If you think hard about why you like the "when+return" solution, I suspect that at some level, you want to take that guard and "get it out of the way" of your cognitive processes, allowing the rest of the function to be the focal point. This (I claim) is a recipe for subtle bugs.
Search your feelings; you know it to be true!
EDIT: an ant just crawled across my laptop. This is a sign that I'm speaking the truth.
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
)