Generalized Argument Removal - SPECIALIZE:RELAX

Let's say you want a version of append that just appends random numbers to things:

>> append-random: adapt get $append [
       value: random 10
   ]

This works, but the function interface still thinks it takes two arguments:

>> append-random [a b c] [d e]
== [a b c 10]

So fine, you can just specialize out the argument. Both ADAPT of a SPECIALIZE and SPECIALIZE of an ADAPT would remove an argument. But you want the specialize to run first, otherwise it would overwrite the adapted value. So SPECIALIZE the ADAPT, to put the specialize earlier in the composition process.

But what value do you put in the specialized-out slot if you're just going to overwrite it?

Today we have quite the awesome answer: use a TRIPWIRE! Sounds great!

>> append-random: specialize (adapt get $append [
       value: random 10
   ]) [
       value: ~<specialized out>~
   ]
** Script Error: append expects [~void~ element? splice?] for its value argument

Ooops. SPECIALIZE Typechecked That

There are two good reasons for specialization to type check:

  • You find out about bad types at the moment of specialization--instead of having to wait until the function is called to know there are problems.

  • It can speed up the system by not type checking those parameters again (though it occurs to me this may be broken right now, and fixing it sooner than later would probably be smart)

Historically what I've done just to get things going is to use some value that typechecks in the argument slot:

>> append-random: specialize (adapt get $append [
       value: random 10
   ]) [
       value: [something that typechecks]
   ]

>> append-random [a b c]
== [a b c 7]

>> append-random [a b c]
== [a b c 9]

>> append-random [a b c]
== [a b c 7]

For the sake of education, notice what happens if you did it backwards and ADAPT the SPECIALIZE:

>> append-random: adapt (specialize get $append [
       value: [something that typechecks]
   ]) [
       value: random 10
   ]

>> append-random [a b c]
== [a b c [something that typechecks]]

Seems We Need SPECIALIZE:RELAX

Having to pick an arbitrary meaningless value that won't trip up the type checking is bad.

While we want to type check 99% of the time, this kind of scenario calls for a version of specialize that does not do typechecking.

Hence I propose SPECIALIZE:RELAX.

Tripwires seem like the go-to datatype to use for these specialized-out values. Rather than just say ~<specialized out>~ you can be as detailed as you like, to help inform on what should happen:

>> append-random: specialize:relax (adapt get $append [
       value: random 10
   ]) [
       value: ~<ADAPT phase of APPEND-RANDOM puts INTEGER! here>~
   ]

Tripwires are great! :boom: You don't have to stress too much about cost...the string inside the tag only exists as one instance in memory.

But you could be lazy and/or cheap, and just unset it. :man_shrugging:

>> append-random: specialize:relax (adapt get $append [
       value: random 10
   ]) [
       value: ~
   ]

There might be other uses for not typechecking at the moment of specialization, but I can't think of what they would be.

Not to deny the utility of this SPECIALISE:RELAX, but this feels like a job for partial application:

append-random: partial [append _ (random 10)]

…well, it would need to make sure the random 10 is run on every function evaluation rather than just once when the function is constructed. But these details can be worked out.

(In Haskell that ‘just works’ thanks to laziness and chucking side-effects into monads. But of course Ren-C doesn’t have either.)

1 Like

Yup! Though there is no fundamental PARTIAL in the core at this time...

Which means the only way to build it is with tools like ADAPT, SPECIALIZE, ENCLOSE, etc.

At one time, I had it so putting GROUP!s in SPECIALIZE slots would run the group. So if you wanted to literally specialize a slot with a group, you'd have to put a group that evaluated to that group.

But I decided that made it a less clean primitive. I figured the ADAPT+SPECIALIZE was more general...and due to the way it's done, not really that much less efficient.

In terms of the technical nuts and bolts: ADAPT doesn't make a new parameter spec (Exemplar) for a function, it just uses what's there. And SPECIALIZE doesn't create a new "implementation array" (Details) for a function, it just uses whatever implementation was there and makes a new parameter spec. So it doesn't cost a whole lot more to adapt a specialization than what could be accomplished by writing a single generator that wove both behaviors into one step.