Defaulting Locals In Functions: Succinct and Speedy

Modern Ren-C lets you define locals in function specs using FENCE!.

>> y: 10

>> foo: proc [x {y}] [
       y: 20
       print ["x plus y is" x + y]
   ]

>> foo 1000
x plus y is 1020

>> y
== 10

Obviously, LOCAL Is Now Available As A Refinement

It's nice to be able to have functions like get-time:local.

And it's also nice that malicious actors can't slip values into your locals at the callsite.

(Yes, people did actually worry about this in historical Redbol and do paranoid checks...!)


You Can Default Locals (and it's efficient)

The FENCE! uses the "CONSTRUCT dialect"... so you can do things like default your locals:

>> foo: proc [x {y (20)}] [
       print ["x plus y is" x + y]
   ]

>> foo 1000
x plus y is 1020

Defaulting locals actually has a performance and storage advantage, because the slots in the frame are a sunk cost. And you avoid running the assignment code on each call.

But because of that: take note that the computation of the local happens exactly once at creation time and is stored in the archetypal frame:

>> z: 300

>> foo: proc [x {y: z + 4}] [
       print ["x plus y is" x + y]
   ]

>> foo 1000
x plus y is 1304

>> z: -300

>> foo 1000
x plus y is 1304

So you can't use it if what you want the local to be is different on each instantiation.


Locals Default To Null

Because it follows the CONSTRUCT dialect, your locals will default to null. But there are other shorthands:

  • If you want your local to be trash, make it a quasiform: proc [x {~y~}] [...]

  • If you want it to be a void, make it a metaform: proc [x {^y}] [...]


You Can Define Locals Anywhere In The Spec

And you can have any number of definitions:

foo: func [a {b ~c~} d :e {f} {^g}] [...]

This is of great benefit to generative code.

(It's also used to build "frame-compatible" natives. Some natives are similar but are missing arguments others expect...and if you throw in locals at positions where an argument would be you can use the same hardcoded position numbers for either frame.)

Why It's Like This: Just Look at R3-Alpha FUNCT (!)

Look how complex the locals-gathering FUNCT from R3-Alpha is, as it tries to add some local variables to a spec...

It had to check to see if there was already a /LOCAL, and add it if not:

; Copy the spec and add /local to the end if not found
unless find spec: copy/deep spec /local [append spec [
	/local ; In a block so the generated source gets the newlines
]]

Bear in mind that specifying /LOCAL twice would be a duplicate refinement error, and /LOCAL did not have to be at the end of a spec. Things were tricky, because there were "private refinements". These refinements were an artifact of how help worked--they were not shown--but weren't actually private.

So you had to be careful, to insert things after local but not after the private refinements, or they'd be arguments to those refinements:

; Collect all set-words in the body as words to be used as locals, and add
; them to the spec. Don't include the words already in the spec or object.
insert find/tail spec /local collect-words/deep/set/ignore body either with [
   ...
]

That's cool... but you can't default refinements?

>> foo: proc [x :y [integer!] (20)] [
       print ["x plus y is" x + y]
   ]
** PANIC: Illegal function spec item: (20)

That seems like it would be useful...

At one point, defaulting was done with higher level code (something like R3-Alpha's FUNCT).

And the defaulting didn't use any special machinery... it actually just grafted the defaulting into the body:

foo: proc [x <local> y (20)] [
   print ["x plus y is" x + y]
]

=> 

foo: proc [x <local> y] [
   y: '20
   print ["x plus y is" x + y]
]

(The generalized code had to LIFT the value to make sure it could be assigned.)

However, because the new mechanics use the archetype frame slots to store the local, there isn't a place to put a refinement's default. That slot is holding the PARAMETER! definition for the types accepted by the refinement.

Of Course, You Can Make Your Own...

There's nothing stopping you from building myfunc or whatever that has defaulted refinements. Sky's the limit, be creative, make whatever... :man_shrugging: I doubt defaulting refinements is going to be that important compared to all the other per-task innovations you might have.

But since the system can't offer any particular benefit here, I don't feel it's worth it to rig it up.

It also raises questions about positioning... refinements have description strings and type spec blocks, which locals do not. I think it makes the spec cluttered, and never really used the feature when we had it.