One way or another LAMBDA needs type checking, so I went ahead and gave this a shot.
As it happens, []: is a bit more apropos than you might think. SET-BLOCK!s which receive more values than they expect will just not do the assignments of the extra values, and pass through the input as-is if no output variable is circled.
>> []: 10
== 10
>> []: pack [10 20]
== \~['10 '20]~\ ; antiform (pack!)
>> error? []: fail "HI"
== \~okay~\ ; antiform (keyword!)
So semantically, it represents a kind of transparent assignment, of something that isn't there.
It goes rather well with LAMBDA's notion of "the result just passes through" / "drops out".
[]: is annoying, but somewhat in a good way.
What I mean by that is that you are swapping between RETURN: and []:, and doing this swap makes you very conscious of the presence or absence of a definitional return inside the construct.
I think this visual sync-up is a great reinforcement of the reality of the situation. It smacks you in the face a bit: there is no RETURN, so if you say RETURN and it executes... you are returning from something else besides this LAMBDA.
I've spoken a bit before on the topic: Warts in Dialecting: When Worse is Better
The Alluring Alternative is Still Just "Leading Block"
intsum: lambda [[integer!] x [integer!] y [integer!]] [
x + y ; want to document this will be an integer
]
When you pick just that example, it looks less jarring. It might make more obvious sense if we standardize RETURN: and YIELD: to force them to always be first in the spec. That would make the position speak specifically to it.
But should it? I talk about the "rhythm breaking" above. Look how things are when you try putting blocks before description strings:
compose2: native [
"Evaluates only contents of GROUP!-delimited expressions in the argument"
return: [
any-list? any-sequence?
any-word? ; passed through as-is, or :CONFLATE can produce
any-utf8?
null? ~word!~ space? quasar? ; :CONFLATE can produce these
]
"Strange types if :CONFLATE, like ('~)/('~) => ~/~ WORD!"/
pattern [any-list? @any-list?]
"Pass @ANY-LIST? (e.g. @{{}}) to use the pattern's binding"
template [<opt-out> any-list? any-sequence? any-word? any-utf8?]
"The template to fill in (no-op if WORD!)"
:deep
"Compose deeply into nested lists and sequences"
:conflate
"Let illegal sequence compositions produce lookalike WORD!s"
:predicate [<unrun> frame!]
"Function to run on composed slots"
]
To my eyes, it looks better done the other way:
compose2: native [
"Evaluates only contents of GROUP!-delimited expressions in the argument"
return: "Strange types if :CONFLATE, like ('~)/('~) => ~/~ WORD!"
[
any-list? any-sequence?
any-word? ; passed through as-is, or :CONFLATE can produce
any-utf8?
null? ~word!~ space? quasar? ; :CONFLATE can produce these
]
pattern "Pass @ANY-LIST? (e.g. @{{}}) to use the pattern's binding"
[any-list? @any-list?]
template "The template to fill in (no-op if WORD!)"
[<opt-out> any-list? any-sequence? any-word? any-utf8?]
:deep "Compose deeply into nested lists and sequences"
:conflate "Let illegal sequence compositions produce lookalike WORD!s"
:predicate "Function to run on composed slots"
[<unrun> frame!]
]
Although some might argue it's better to space the whole thing out, e.g. @rgchris's curl goes on like this:
curl: func [
"Wrapper for the cURL shell function"
url [url!]
"URL to Retrieve"
:method [word! text! blank!]
"Specify HTTP request method"
:send [text! binary! file! blank!]
"Include request body"
:header [block! object! blank!]
"HTTP headers"
:as [text!]
"User agent"
:user [text! blank!]
"User Name"
...
]
Under this system, if that were a lambda, with a typespec for the result, you would get:
curl: lambda [
"Wrapper for the cURL shell function"
[text! binary!]
"Result is BINARY! if you use :BINARY refinement"
url [url!]
"URL to Retrieve"
:method [word! text! blank!]
"Specify HTTP request method"
...
]
I kind of don't like the naked block, there. It's just kind of floating, without any lexical "I'm an output" signal. I prefer the []: to be there, so you at least know what to look up:
curl: lambda [
"Wrapper for the cURL shell function"
[]: [text! binary!]
"Result is BINARY! if you use :BINARY refinement"
url [url!]
"URL to Retrieve"
:method [word! text! blank!]
"Specify HTTP request method"
...
]
I'm still a bit torn, but I now feel that I KNOW that RETURN: is DEFINITELY the wrong answer.
At times I've thought "oh, what the hell, just use RETURN: and move on, people will know what it means, even though the function doesn't have 'a return'". But I'm glad to have not done that, because it's misleading and very bad.
When you realize why it's bad, and take into account the benefit of the "wart" reminding you there is no name for the return function here... I think the case for []: is pretty compelling.