So the ARRAY mezzanine from R3-Alpha allowed you to make an array with an initial value that could be a function...in which case it evaluated that function each time:
r3-alpha>> array 3
== [none none none]
r3-alpha>> array/initial 3 10
== [10 10 10]
r3-alpha>> n: 0 array/initial 3 does [n: n + 1]
== [1 2 3]
This raises the question of how you would make an array whose initial value was itself a function.
If you're a loophole-minded person, you might notice you could trick this particular case by making a function that returns a function.
r3-alpha>> array/initial 3 does [:append]
== [#[action! [...]] #[action! [...]] #[action! [...]]]
But that's not really a general answer for how to deal with this kind of polymorphism--it just works if you're using the result purely to do a substitution.
Trying This With Antiform Actions
I've loosened my stance on isotopes in function frames as parameters. It's not a good idea to take them as default, but forcing you to make every parameter ^META just to get isotopes is limiting.
So I gave this a shot...
array: func [
{Makes and initializes a block of a given size}
return: "Generated block or null if blank input"
[block!]
size "Size or block of sizes for each dimension"
[<maybe> integer! block!]
/initial "Initial value (will be called each time if action isotope)"
[any-element? action?]
]
So that [any-element? action?] leads to a willingness to accept frame antiforms, as well as anything you can put in a block. Having such an annotation leads you into the "danger zone" where if you don't prefix accesses to INITIAL with a colon, you run the risk of running a function. But if you don't have the annotation, you don't have to be paranoid and decorate your references.
Here's how the INITIAL references wind up in the implementation:
case [
block? rest [
repeat size [append block (array/initial rest :initial)]
]
action? :initial [
repeat size [append block run :initial] ; Called every time
]
any-series? initial [
repeat size [append block (copy/deep initial)]
]
] else [
append/dup block initial size
]
So the BLOCK! case which recurses uses the :INITIAL for pass-thru.
I used RUN :INITIAL when I could have just used INITIAL, simply because it feels more clear. If this wasn't already contained by being at the end of a block, it would be better to limit the parameterization by saying something like apply :initial []
or the now-equivalent initial :: []
And It Works
What we're kind of accepting as a default is that function generation produces isotopes. So getting a plain action requires some kind of extra step, like REIFY
>> func [x] []
== ~#[frame! [x]]~ ; anti
>> action: reify func [x] []
== ~#[frame! {action} [x]]~
>> action ; not word!-active
== ~#[frame! {action} [x]]~
>> ap: reify :append
== ~#[frame! {ap} [series value /part /dup /line]]~
>> ap
== ~#[frame! {ap} [series value /part /dup /line]]~
What this means is that you get a similar behavior to before, where if you pass a "live" function you've fetched from a word or just generated, then it runs. You have to do something extra to it to get it to be inert.
>> array 3
== [_ _ _]
>> array/initial 3 10
== [10 10 10]
>> n: 0 array/initial 3 does [n: n + 1]
== [1 2 3]
>> array/initial 3 reify does [n: n + 1]
== [~#[frame! []]~ ~#[frame! []]~ ~#[frame! []]~]
>> array/initial 3 reify/unquasi does [n: n + 1]
== [#[frame! []] #[frame! []] #[frame! []]]
That Looks Like An Isotope Success Story...
It doesn't require any convoluted thinking to get the literal vs. non-literal distinction. And you can apply this technique to cases that don't have a weird workaround.
I wish I could say that all of antiform action impacts were as well sorted out as this looks. But it's a start, and it shows the potential.