Notation for Outputs in Function Specs

The first cut at multiple return values made a multi-return output look like:

 func [/foo [<output> integer!]] [...]

Which was expedient to try it out. But dialect-wise, I think a SET-WORD! makes more sense:

 func [foo: [integer!]] [...]

But What About The Deviant RETURN: ?

It's a bit confusing to have RETURN: work different than other things specified by SET-WORD!. This could be avoided by using a different convention, e.g. <return> [integer!] to stress the difference.

Or we could just say that it's "one of those things" and learnable, that the act of naming a return value "RETURN:" implies it's the main return...and gets special conventions and handling.

3 Likes

So we have a new reason to start questioning RETURN:. And that is how actions that lack RETURN functions might nevertheless want to document their potential return product.

intsum: lambda [x [integer!] y [integer!]] [x + y]

We could just say that you put return: [integer!] in the spec anyway, and understand that there's no actual return function. But I feel like it's good to reinforce that there isn't.

So this suggests maybe even having it be something like #returns or #result

intsum: lambda [
    #result [integer!]
    x [integer!]
    y [integer!]
][
    x + y
]

Or we could think about dropping a label entirely, and saying you just understand the first block is a description of the result:

intsum: lambda [
    [integer!]
    x [integer!]
    y [integer!]
][
    x + y
]

But the speedbump on that is that it flies in the face of my preferred syntax, which is to put the parameter label before the type spec. This provides a good rhythm that you don't need to break when you have refinements with no arguments.

You'd have to put it after, or it would be considered part of the function description.

intsum: lambda [
    {An Integer Sum Expressed as a Lambda}
    [integer!] "Result is the sum of first and second"
    x "The first value"
        [integer!]
    y "The second value"
       [integer!]
][
    x + y
]

I guess my thought is that SET-WORD! looks nice, and result: is a pretty value-neutral thing that would help you not be confused by tying it up to a RETURN operation which the function might not have.

intsum: lambda [
    {An Integer Sum Expressed as a Lambda}
    result: "Result is the sum of first and second"
        [integer!]
    x "The first value"
        [integer!]
    y "The second value"
        [integer!]
][
    x + y
]

Mechanically, New Ideas Are Coming

It may be that the way to typecheck a lambda in the event you want that is to use another component:

intsum: typechecked [integer!] lambda [
    {An Integer Sum Expressed as a Lambda}
    x "The first value"
        [integer!]
    y "The second value"
        [integer!]
][
    x + y
]

And it's just that FUNCTION comes and rolls that all together for you. Which is an interesting thought, and maybe a more coherent way to think about it!

In practice, I've found that having the colon at the end of the parameter makes it harder to see when something is an output. Look at transcode for example, of how it's hard to see:

transcode: native [
    {Translates UTF-8 source (from a text or binary) to values}
    return: "Transcoded value (or block of values)"
        [any-value!]
    next: "Translate one value and give back next position"
        [text! binary!]
    relax: "Try to trap errors and skip token (toplevel only)"
        [error!]
    source "If BINARY!, must be Unicode UTF-8 encoded"
        [text! binary!]
    /file "File to be associated with BLOCK!s and GROUP!s in source"
        [file! url!]
    /line "Line number for start of scan, word variable will be updated"
        [integer! any-word!]
    /where "Where you want to bind words to (default unbound)"
        [module!]
]

At a glance it's a bit easy to miss.

Not only that, there's no universal rule that outputs have to be in the front of the parameter list. (This is so you can add outputs to functions after the fact in compositions built on top of existing functions.)

What If @WORD Was Used Instead?

The ergonomics seem better, they jump off the page:

transcode: native [
    {Translates UTF-8 source (from a text or binary) to values}
    return: "Transcoded value (or block of values)"
        [any-value!]
    @next "Translate one value and give back next position"
        [text! binary!]
    @relax "Try to trap errors and skip token (toplevel only)"
        [error!]
    source "If BINARY!, must be Unicode UTF-8 encoded"
        [text! binary!]
    /file "File to be associated with BLOCK!s and GROUP!s in source"
        [file! url!]
    /line "Line number for start of scan, word variable will be updated"
        [integer! any-word!]
    /where "Where you want to bind words to (default unbound)"
        [module!]
]

Your eye can just jump down to where the normal parameters begin.

It feels more coherent to use a different "part of speech" for RETURN: (or whatever it is) because it's really a completely different beast.

If there are no multi-returns, then the result would abut right up against the other parameters...but if RETURN: or RESULT: or whatever is the only colon-oriented thing you see then that's a special keyword so I don't think it's a problem. You won't confuse it because you know the word is called result--the part of speech isn't the cue.

Or Is The Answer To Rely On Syntax Highlighting?

If one wants to argue that a SET-WORD! is the right semantic thing, and that something that makes SET-WORD! stand out as bold is a better answer vs. messing with the parameter convention.

1 Like

I tried this out to see what I thought, going through the system and absorbing the impact. It's good in most ways and only a little bit off in others.

The thing that bothers me the most is that there's a phenomenon already associated with multi-returns and the @ symbol, and that's circling the return result you want to use. While set-words have no conflict in "return domain mindspace".

Also, there's something to be said for the function spec being less jarring... kind of like how quoted words being less jarring is good. If you're not specifically tending to the spec of the function, then you want it to look kind of english-like. This pushes it away from that.

But there's a very major benefit I hadn't thought of. Consider what happens in a function where the parameters aren't labeled:

foo: func [out: in] [...]
foo: func [@out in] [...]

It feels like in a dialect, something with a SET-WORD! should have a thing always after it that it relates to assigning. So having something that's naming a field in the function that doesn't need anything to the right of it has kind of a bad feel for a parameter.

That makes it feel like it pushes things over the edge to "we should change it".

1 Like

How about using get-word?
Get-words have the connotation of getting something out already.

foo: func [:out in] [...]

GET-WORD! has a fair amount of contention for it already. Right now it means "colon escapable literal".

A plain literal argument (indicated by a quote mark) is unescapable:

>> unescapable: func ['x] [print mold x]

>> x: 10

>> unescapable x
x

>> unescapable :x
:x

>> unescapable (1 + 2)
(1 + 2)

>> unescapable :(1 + 2)
:(1 + 2)

But using the prefix colon seemingly semiotically asks for the escape:

>> escapable: func [:x] [print mold x]

>> x: 10

>> escapable x
x

>> escapable :x
10

>> escapable (1 + 2)
(1 + 2)

>> unescapable :(1 + 2)
3

This is a more coherent version of what R3-Alpha and Red do (they backwardsly use 'x for escapable and :x for unescapable, and because there's no GET-GROUP! they have to make plain groups do escaping).

But I've wondered if that should instead be ':x instead, where :x could be used for a generalized subversion of callsite evaluation:

Beating REPEND: A New Parameter Convention?

Things are sort of fibrillating in that space right now, with potential new meanings for GET-WORD!, so it all is about to come under review.

But I was happy enough with the @word for outputs that I went ahead and did it. So far I give it a stamp of approval, so I'll see if this happiness wears of or persists!

1 Like

It's wearing a little.

Here's another idea. What if SET-WORD! was used, but as a label. A short word like OUT that you'd recognize, so it's harder to miss:

transcode: native [
    {Translates UTF-8 source (from a text or binary) to values}
    returns: "Transcoded value (or block of values)"
        [any-value!]
    out: next "Translate one value and give back next position"
        [text! binary!]
    out: relax "Try to trap errors and skip token (toplevel only)"
        [error!]
    source "If BINARY!, must be Unicode UTF-8 encoded"
        [text! binary!]
    /file "File to be associated with BLOCK!s and GROUP!s in source"
        [file! url!]
    /line "Line number for start of scan, word variable will be updated"
        [integer! any-word!]
    /where "Where you want to bind words to (default unbound)"
        [module!]
]

I think we have to commit to the idea that these are passable in as variables (e.g. via APPLY and such). So making them look like refinements might help visibility

transcode: native [
    {Translates UTF-8 source (from a text or binary) to values}
    returns: "Transcoded value (or block of values)"
        [any-value!]
    out: /next "Translate one value and give back next position"
        [text! binary!]
    out: /relax "Try to trap errors and skip token (toplevel only)"
        [error!]
    source "If BINARY!, must be Unicode UTF-8 encoded"
        [text! binary!]
    /file "File to be associated with BLOCK!s and GROUP!s in source"
        [file! url!]
    /line "Line number for start of scan, word variable will be updated"
        [integer! any-word!]
    /where "Where you want to bind words to (default unbound)"
        [module!]
]
1 Like

This was always a kind of random choice, made for visibility vs. any real philosophy of why it meant anything.

Now that pragmatism has driven a binding-related interpretation of @word, I think this pushes into a real purpose for @word parameters... which is to quote with binding.

 >> foo: func [@var] [
        print ["The value is" get var]
    ]

>> x: 10

>> foo x
The value is 10

If you just used a regular quote on the parameter, then you would not get a binding:

 >> foo: func ['var] [
        print ["The value is" get var]
    ]

>> x: 10

>> foo x
** Error: x is unbound

If the concern is just visibility, maybe outputs and returns should use tags.

transcode: native [
    "Translates UTF-8 source (from a text or binary) to values"
    <return> "Transcoded value (or block of values)"
        [any-value!]
    <out> rest "Translate one value and give back next position"
        [text! binary!]
    source "If BINARY!, must be Unicode UTF-8 encoded"
        [text! binary!]
    /one "Translate one value and give back next position"
    /file "File to be associated with BLOCK!s and GROUP!s in source"
        [file! url!]
    /line "Line number for start of scan, word variable will be updated"
        [integer! any-word!]
]

To reiterate: one reason for using a tag for RETURN would be so that lambdas could say what they returned, while still having locals or arguments named return via a WORD!.

In any case... I don't know the answer, but @ will be taken for a "bind this parameter" type, so something needs to be done about the multi-return output notation.

1 Like