Hacker News new | ask | show | jobs
by S1ngleM4lt 1428 days ago
As someone who had recently started going the other direction, from C and C-based languages to those more influenced by lisp, it took a bit of getting used to but I’ve grown to appreciate the expressiveness and composability. The only thing I can think of that statements allow, but as I understand it pure expressions don’t, would be early returns. The closest replacement I’ve seen is to just make “return” raise an exception and wrap the whole expression with the try/catch equivalent, which isn’t terrible but was a bit surprising coming from a C-based background where returning early is a fairly common pattern.
1 comments

You shouldn't need to raise exceptions for early returns. You can usually restructure your program to provide for the proper return without needing to use a return or comparable form when using an expression oriented language. If you had this C snippet, for instance:

  int foo(...) {
    if (condition) return an_expression;
    ...
    return another_expression;
  }
You'd put everything after that if into the else part instead of dropping it (like you would in C):

  (defun foo (...)
    (if condition
      an-expression
      (progn
        ...
        another-expression))) ;; and this /progn/ form would possibly be extracted to a new function if it was too long
Or more likely a cond if using Common Lisp and there were more than two cases:

  (defun foo (...)
    (cond (condition an-expression)
          ...
          (t
             ...
             another-expression)))
And in Common Lisp (and some others) you can still get a return form, using return-from in particular, from functions if you want to keep something more like the C-ish form:

  (defun foo (...)
    (when condition (return-from foo an-expression))
    ...
    another-expression))
Thanks, learned something new today with return-from! I’d be curious how different cl implementations implement it.

The first few examples are what I was more familiar with, and it is precisely your note “and this /progn/ form would possibly be extracted to a new function if it was too long” which makes me want to just return early in some cases. Sometimes a case is common enough that it warrants a separate function, but having to define functions for incredibly specific cases just to avoid excessive indentation is not ideal for me.

> Thanks, learned something new today with return-from! I’d be curious how different cl implementations implement it.

At least in SBCL, with it compiling the function, it turns into exactly the kind of assembly code you'd expect. Using disassemble:

    CL-USER> (defun foo (n)
               (when (< n 0) (return-from foo 20))
               (* 20 n))

    CL-USER> (disassemble #'foo)
    ; disassembly for FOO
    ; Size: 54 bytes. Origin: #x53642F41                          ; FOO
    ; 41:       498B4510         MOV RAX, [R13+16]                ; thread.binding-stack-pointer
    ; 45:       488945F8         MOV [RBP-8], RAX
    ; 49:       488B55F0         MOV RDX, [RBP-16]
    ; 4D:       31FF             XOR EDI, EDI
    ; 4F:       FF14252001A052   CALL QWORD PTR [#x52A00120]      ; SB-VM::GENERIC-<
    ; 56:       7C16             JL L1
    ; 58:       488B55F0         MOV RDX, [RBP-16]
    ; 5C:       BF28000000       MOV EDI, 40
    ; 61:       FF14251001A052   CALL QWORD PTR [#x52A00110]      ; SB-VM::GENERIC-*
    ; 68: L0:   488BE5           MOV RSP, RBP
    ; 6B:       F8               CLC
    ; 6C:       5D               POP RBP
    ; 6D:       C3               RET
    ; 6E: L1:   BA28000000       MOV EDX, 40
    ; 73:       EBF3             JMP L0
    ; 75:       CC10             INT3 16                          ; Invalid argument count trap
This sets up the call for the comparison, conditionally jumps based on the result (JL), and then returns one of the two results. Note that the "40" in 5C and 6E are really "20", this is a consequence of how many CL implementations handle integers, the low-order bit is used as a tag so not really part of the value. If you wrote it in C it would be very similar.

https://godbolt.org/z/cYPhdW3qd

The biggest difference is that the C version doesn't need a call for the comparison.