Implicit Execution of RETURN in functions = ...BAD (?)

I've been back to working on stackless, and am dead-set on getting it merged in.

This puts me face to face with some tough issues surrounding function implementations and RETURN.

No Feedback Here So I Have To Go After It

@gchiu has been living under a rock (well, in New Zealand :sheep: same difference). So he never even noticed Ren-C supports RETURN: specs. Hence any RETURN he's ever used has been in a FUNC[TION] without a return spec.

But I think this does align with the reality that people expect FUNCTION to offer a return, out of the box...even with no return spec.

Guess this kills the idea that you have to put RETURN: in the spec to get a return. But I'm still not at ease with the idea of "hiding" a secret call to RETURN under the hood of functions to get type checking.

I Hate To Say It, But... :man_facepalming: Let's Do What JavaScript Does

In JavaScript you don't have to put a RETURN in a function. But if you don't use RETURN, the result is undefined.

We'd do the same, except with "none" (the ~ isotope, value used to mark unset variables):

>> scrambledrop: func [str] [take reverse str]

>> str: "Test"

>> scrambledrop str

>> str
== "seT"

Because there was no RETURN, the SCRAMBLEDROP gave back nothing... e.g. not the #T that came back from the take. But it didn't error either.

JavaScript gives the alternative of lambda functions, which evaluate to the body with an implicit RETURN.

We would do the same. Except their lambdas have a return (you don't have to use it)...but our lambdas actually wouldn't have a RETURN of their own as an option, they'd only have a RETURN from any enclosing functions:

  >> increment: lambda [x] [x + 1]

  >> increment 1019
  == 1020

  >> decrement: x -> [x - 1]

  >> decrement 305
  == 304

(Not having their own RETURN is important for lambdas used as branches of conditionals today.)

For the Near Term, I'll Make FUNC with no RETURN Raise an Error

Since people have been depending on the result "falling out", we'll have an intermediate period where no RETURN will just error.

This is a good time to go through and shore up your RETURN: specs with types, because one thing RETURN gets you is typechecking. (I explain above why RETURN is the locus of typechecking, and in the implementation it will be the only place for typechecking.)

And if you feel that you don't want or need a RETURN, go ahead and embrace LAMBDA. If you really are writing something quick and dirty where all the hassle of a spec is more than you want... why not go all the way with the arrow?

increment: function [return: [integer!], x [integer!]] [  ; what a hassle!
    return x + 1
]

increment: function [x] [  ; at least there's no verbose type specs
    return x + 1
]

increment: func [x] [  ; hey, an abbreviation
    return x + 1
]

increment: lambda [x] [  ; lost the abbreviation but at least no RETURN
    x + 1
]

increment: x -> [x + 1]  ; behold, laziness Nirvana!

Once Again, You Are The Arbiter of Your Experience...

Because I want to emulate Rebol2 and Red, I'm going to always look to make sure there are ways for everyone to get what they want. And when you can take module isolation for granted as working properly... you should still be able to interoperate with other codebases--even if you pick something fundamentally different.

So if you wind up wanting it, you can say:

func: adapt :func [
    body: compose [return (as group! body)]
]

With this, you'll get these semantics:

bar: function [
    return: [integer!]
    arg [logic!]
][
    return: adapt :return [value: value + 300]
    if arg [
       return 4
    ]
    720
]

>> bar true
== 304

>> bar false
== 1020  ; the injected RETURN ran behind the scenes

@gchiu seemed to think that was sensible.

But I like things being less sneaky, I'd be more in favor of:

func: adapt :func [
    any [
         find spec [return: <none>]
         find spec [return: <void>]
    ] else [
        body: compose [
            (as group! body)
            fail "HEY YOU FORGOT YOUR RETURN!"
        ]
    ]
]

But this is where the adaptability is the great strength. I make the core coherent and work. Then you configure it how you like.

I think not erroring--but not letting a result drop out--is a good compromise. It lets people who want to write "procedure"-like things do so without being forced to put in a RETURN they don't want.

1 Like