Lisp was the first language I used (other than assembly code) so when I encountered C I was puzzled why they even bothered to have statements at all. 40 years later I still don't understand the point.
Lisp was the first language I used (other than assembly code) so when I encountered C I was puzzled why they even bothered to have statements at all. 40 years later I still don't understand the point.
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))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.
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.
To me, this indicates there is some merit to the statement/expression distinction.
1 2 3
which are three expressions, and probably the programmer meant "123". Of course: 1; 2; 3;
is OK. (+ 1 2)
as opposed to: 1+2
Whether the gain is worth it is, I agree, debatable.In C, `1+2` is an expression that returns 3. `1+2+3` acts like two expressions (and perhaps it is; I forget the precise terminology used in the standard). You can write `return foo(1+2)`.
But `for` introduces a statement so can only be used where a statement can.