Issues With Querying A Function's "RETURN"

Each FUNCTION has a local variable named RETURN. When it's running, the cell for that variable points at an "archetypal return function". But that cell also holds a reference to that specific function instantiation's frame. This way it knows how to return specifically from that invocation of that function.

This "definitional return" concept has worked quite well. But there are some fiddly issues that arise from trying to make RETURN an ordinary local variable.

It Was Hoped RETURN Would Not Be Special

The idea is that LAMBDA would be lower-level than FUNCTION, and permit you to have parameters or local variables named RETURN that had nothing to do with returning.

/demo: lambda [return continue] [
    return + continue
]

>> demo 1 2
== 3

So an aspirational aspect of the design was that a user could create something that operated identically to the FUNCTION abstraction (just a bit slower, due to not being implemented natively).

It would use something like THROW and CATCH, to implement RETURN (or construct(s) in that family) itself, as LAMBDAs synthesized upon instantiation.

RETURN as Local Hides Its Type Information

While it's not an argument to the function, RETURN still has associated type information.

Since RETURN is a local that will be filled with an action when the function runs, it isn't using its frame slot until that invocation happens. That means it can store a PARAMETER! which says what return types are legal. (It seems like a good use of an otherwise-unused slot otherwise you'd need a slot in the Phase to hold it.)

Functions like HELP want to know those types. Yet locals and specialized arguments are traditionally considered implementation details, hidden from callers. They're only visible when frames are captured during the "phase" of being inside the function call:

>> /foo: function [return: [integer!] x] [
     print mold words of binding of $x
]

>> words of foo/
== [x]

>> foo
== [return x]

Today's unsatisfying solution is that there's a flag on parameters that represent RETURN. If you want the type information of that return, you can ask for it:

>> return of foo/
== ~#[parameter! [integer!]]~  ; anti

But this flag is only applied by the FUNCTION native when its construction the parameter list. So you wouldn't (for instance) get this behavior on a usermode re-implementation of FUNCTION based on LAMBDA.

How To Change RETURN In Composition?

Let's say you're going to build a higher-level function with something like ENCLOSE:

>> /bar: enclose foo/ func [return: [tag!] f [frame!]] [
       print "I'm an enclosure!"
       eval f
       return <tag>
   ] 

The interface of an enclosed function is the same parameter list as the original, and the same returns as the original. The enclosure is implemented as a function that takes a frame, and it can be given a new return type. But today that isn't reported as the return type if you asked for HELP of FOO. It would look at the original parameter list, which in this case says its RETURN is [integer!].

This suggests that the answer to the question return of has to be dispatcher-specific.

FUNCTION would say "it's the contents of the RETURN local in the archetype (not overwritten with return function, so still a PARAMETER!)".

ENCLOSE would say "it's the return type of the enclosure function that takes the frame" but then give back the original parameter list for everything else. (This raises a question of how you would inherit the original function's return... it might be nice if you used a LAMBDA or something that reported no return constraint, that it would default to the constraints of the function you were enclosing.)

In any case, if it was answered by the dispatcher... then if LAMBDA says "I don't have a RETURN", you'd not have HELP information on your RETURN if you were trying to build your own FUNCTION on top of LAMBDA.

The word RETURN may be a distraction, here. Because if it's something like a GENERATOR, the products are coming from calls to YIELD. Maybe (result of ...) or (synthesis of ...) would help stress that this question isn't tied up specifically with RETURN.

LAMBDA Semantics May Want Result Specification

The idea of LAMBDA is that the evaluative product just drops out (see various writings on why FUNCTION does not allow that).

But just because you want that semantic, doesn't mean you don't want the HELP to not give a return type.

However, LAMBDA doesn't have anywhere to store the type information the way FUNCTION does. If it has a RETURN it may be using it for unrelated purposes.

Given what I've proposed for the dispatcher-specific RETURN, you could accomplish it with an ENCLOSE:

 enclose (lambda [...] [...]) func [return: [<spec>] f] [return eval f]

But it seems clearly better to have something specific to this purpose:

 returns [<spec>] lambda [...] [...]

Then the Returns_Dispatcher() would simply type check what you gave it.

Perhaps it could even accept a function as a first parameter, and then just say it returns whatever that function returns:

returns add/ lambda [x] [x + 1]

Though that might be more confusing than writing it out:

returns (return of add/) lambda [x] [x + 1]

Maybe a refinement should be used to bless that you're doing it on purpose:

returns:same-as add/ lambda [x] [x + 1]

I guess the big question would be how to get the return's textual description in there. Maybe it could assume if the format is TEXT! BLOCK! that's what you mean:

/foo: returns [
   "The input plus one"
   [integer!]
] lambda [x] [
    x + 1
]

Well, There's Some Ideas

I guess the idea here is that mechanics for answering "what do you return" come from the function's dispatcher, and maybe that becomes a chained question where if the dispatcher wraps something else, it has to ask the right parts of that thing. There may be no user-exposed mechanic for building your own answer to the question beyond something like RETURNS, which is probably good enough.