7

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.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Roguebantha
  • 814
  • 4
  • 19

4 Answers4

10

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 returns 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.

John Clements
  • 16,895
  • 3
  • 37
  • 52
9

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.

soegaard
  • 30,661
  • 4
  • 57
  • 106
5

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
   )
Community
  • 1
  • 1
Throw Away Account
  • 2,593
  • 18
  • 21
  • 2
    Why do you go through all those hoops and bounds to capture the `return` identifier? Something like https://gist.github.com/leppie/887a278e4db0db99b031 works fine in R6RS. Am I missing something? – leppie Apr 29 '15 at 05:05
  • @leppie Nope. You didn't miss a thing. I actually didn't know you could do that. – Throw Away Account Apr 29 '15 at 05:26
  • In Racket, the appropriate way to give special keywords meaning inside of a macro is with syntax parameters. `(define-syntax-parameter return #f) (define-syntax-rule (define/return header body ...+) (define header (let/ec return-continuation (syntax-parameterize ([return (make-rename-transformer #'return-continuation)]) body ...))))` This avoids breaking hygiene or the need to use `syntax-case` / `syntax-parse` instead of the much simpler `define-syntax-rule` – Jack Apr 30 '15 at 02:37
  • @Jack syntax parameters can be shadowed by local variables, making them no better than globals. I went into detail about it [here](http://stackoverflow.com/a/29272495/4230643). – Throw Away Account Apr 30 '15 at 02:58
  • @ThrowawayAccount3Million Why on earth is the ability to shadow syntax parameters a *bad* thing? It means they respect lexical scope. If I did `(splicing-let ([return something-else]) (define/return ...))` I would *expect* `return` to refer to the closest definition of return in scope. Syntax parameters aren't used to tie a macro to a certain global identifier, they're used to make it explicit that a macro depends on some contextual binding. And lexically scoped bindings are the easiest bindings to reason about. – Jack Apr 30 '15 at 03:08
  • @ThrowawayAccount3Million You *can't* get your `return` keyword shadowed by another library. Either you've defined `return` in the module you're using it in, in which case lexical scope dictates it shadows any provided `return` binding from anything you've required, or you're requiring your `define/return` macro from a library that provides return in which case it can't get shadowed by a `return` binding provided from any other module because a module requiring two libraries that provide the same bindings is a syntax error. As long as you're respecting lexical scope and using hygienic macros.. – Jack May 02 '15 at 07:57
  • ...the case you're worried about is literally not possible. That's the whole advantage of hygiene and lexical scoping - easier to reason about, better static analysis, and fewer "gotchas". – Jack May 02 '15 at 07:58
  • @Jack I had written a Racket version of CL's LOOP macro where the `=` operator didn't work because my macro was written in Swindle, but most of my programs were written in Racket, which defines a completely different `=` and therefore the `=` in my Racket code didn't pattern-match with the `=` in the macro. I had no idea what was going on. The other keywords worked fine. Therefore, I see syntax shadowing as a bug and not a feature. – Throw Away Account Aug 11 '20 at 17:09
0

Simple:

(and (x) y)

The problem is that "when" doesn't return a value. "and" does.

  • 1
    no, this is incorrect, unfortunately. Both do the same thing, in the context of this question - both "happily carry on to the next [code] line" and do not immediately return from the function. – Will Ness Aug 16 '19 at 14:52