Rethinking `<static>` in the Function Spec Dialect

In R3-Alpha, the FUNCTION construct was built on top of FUNC, and added features like statics as a refinement. It looked pretty awkward, because the static was added at the end of the expression:

 r3-alpha>> foo: function/with [x] [return staticvar: add staticvar x] [staticvar: 0]

 r3-alpha>> foo 10
 == 10

 r3-alpha>> foo 20
 == 30

Ren-C tried extending the function spec dialect to support this in a nicer way:

 foo: function [x <static> staticvar (0)] [return staticvar: add staticvar x]

The low-level FUNC implementation doesn't know what <static> is, so it's added by a higher layer, that makes things slower... and since the function spec dialect is kind of foundational it may be the wrong place to be putting this for the core.

@hiiamboris approaches this differently:

It's not the first time I've thought it would be a better direction to break it out. But putting it all as part of the function spec was supposed to have an advantage in that when the body was walked to create the copy, the binding to the static members would be done as well. This is no longer applicable, because the bodies of functions are largely left unbound...

We do lose a feature of noticing when you are naming the static the same thing as something in your function frame and you don't get an error in that case, but maybe you don't want an error (perhaps you inherited the frame through an adaptation or something like that, and you don't care about the frame variable).

Boris's dialect is a bit overloaded, and Ren-C has more parts to help with that...but it would help even more if there were FENCE!

foo: func [x] with [
    {staticvar: 0}
][
    return staticvar: add staticvar x
]

foo: func [x] with {staticvar: 0} [
    return staticvar: add staticvar x
]

So this wouldn't be confused with any other WITH things you were doing, like trying to use objects or words and add them to a block that already had a binding.

>> body: [keep staticvar: staticvar + x]

>> collect [
       wrapper: func [x] with ['keep {staticvar: 0}] body
       wrapper 1
       wrapper 10
       wrapper 100
   ]
== [1 11 111]

(Just trying to drum up a little excitement for FENCE! there, but I think it's the tip of the iceberg.)

Anyway, there've been educational lessons from showing that you can extend the FUNC spec dialect and build higher level features... but I think we should probably tear those out of the core and move to something like this.

1 Like

I thought I was going to like the "BIND body" but it has gotten wordier:

foo: function [x] bind construct [staticvar: 0] [
    return staticvar: add staticvar x
]

I thought this was going to be able to be a FENCE!, but if BIND is going to let FENCE! not act as wrap it would have to take its argument literally (it might...)

foo: function [x] bind {staticvar: 0} [
    return staticvar: add staticvar x
]

So that's not really a done-deal yet.

Still, FUNCTION is taking its argument literally now, so it can take FENCE! in order to gather top-level set-word as local. Hence if you're going to prebind the body, you need a GROUP!

foo: function [x] (bind construct [staticvar: 0] [
    return staticvar: add staticvar x
])

In practice specs tend to be longer:

foo: function [
    "specs are usually multi-line
    x
] (bind construct [
    staticvar: 0
][
    return staticvar: add staticvar x
])

This isn't very pleasing. I have to confess, I miss the in-spec statics.

foo: function [
    "specs are usually multi-line"
    x
    <static>
    staticvar: 0
][
    return staticvar: add staticvar x
]

We could make a refinement to function, that using positionality could put the statics up front:

foo: function:statics [
    "specs are usually multi-line"
    x
][
    staticvar: 0
][
    return staticvar: add staticvar x
]

There could be a designation in the locals FENCE! for things that are statics :-/

foo: function [
    "specs are usually multi-line"
    x
    {$staticvar: 0, local-one local-two}
][
    return staticvar: add staticvar x
]

That's a neat direction, though not very literate. Could use tags there.

foo: function [
    "specs are usually multi-line"
    x
    {<static> staticvar: 0, local-one local-two}
][
    return staticvar: add staticvar x
]

That's somewhat pleasing. But if you're defining 10 statics it sucks to have to say <static> on each one. Which makes the $staticvar: seem pretty nice... and it feels learnable.

Alternative: "It's Statics, Jim, Just not as we know them"

My idea was that FENCE! would imply top-level locals gathering when used as a function body.

But there's another possibility if you take a FENCE! literally: let it WRAP, but then eval it like a GROUP!, expecting its evaluative product to be a BLOCK!

foo: function [x] {
    staticvar: 0
    [
        return staticvar: add staticvar x
    ]
}

At first this seemed like it might be promising, but... it kind of sucks. Locals-gathering is a much higher-leverage concept, compared to the relatively-rare statics.

Or... Just Wrap The Whole Thing

foo: eval {
    staticvar: 0

    function [x] [
        return staticvar: add staticvar x
    ]
}

@rgchris uses this style a lot. It costs you an indent level, and it pushes your function spec way down. It's basically the same thing, except it's compatible with passing a FENCE! as the function body to mean "gather locals".

It's not a bad style. It's certainly better than the BIND CONSTRUCT mess is turning out to be (if what you're doing isn't more complex metaprogramming that requires it)

But what nags me about this is that if I am looking at foo:, as a reader I want to see the spec there next to the SET-WORD, not a bunch of helpers... and then have to scroll pages down before I see the thing being defined.

  • spec should come before helpers

  • helpers should come before body

    • R3-Alpha's FUNCT had /WITH that put them after, which is the worst idea