The HIJACK functionality was proposed by @gchiu, many years ago:
>> /foo: func [x] [print ["FOO" (x + 1)]
>> /foo-reference: foo/
>> foo 20
FOO 21
>> foo-reference 20
FOO 21
>> /bar: func [x] [print ["HIJACKED!" (x + 1000)]
>> hijack foo/ bar/
>> foo 20
HIJACKED! 1020
>> foo-reference 20
HIJACKED! 1020
And so, way back when... I worked up a mechanic for how to do this. There's some nuance to how efficient it can be, based on whether the thing you're hijacking uses a parameter list that's in the same "derivation chain".
While it was originally a somewhat fringe feature, it became central to certain parts of the implementation--including the web ReplPad.
What If You Want To Use The Old Implementation?
Note that all references to the old function will run the hijacker. What if you wanted the old implementation?
The seemingly simplest answer would be to return the old implementation under a new identity by HIJACK:
>> /foo: func [x] [print ["FOO" (x + 1)]
>> /bar: func [x] [print ["HIJACKED!" (x + 1000)]
>> /foo-old: hijack foo/ bar/
>> foo 20
HIJACKED! 1020
>> foo-old 20
FOO 21
But I Did Something Different...
In that old era of ACTION! and FRAME! as distinct types, I decided to be clever.
I said that COPY of an ACTION! would create a new action identity that ran the same code... but that wouldn't be affected by a HIJACK of the old action.
>> /foo: func [x] [print ["FOO" (x + 1)]
>> /bar: func [x] [print ["HIJACKED!" (x + 1000)]
>> /foo-copy: copy foo/
>> hijack foo/ bar/
>> foo 20
HIJACKED! 1020
>> foo-copy 20
FOO 21
So HIJACK didn't return anything.
On the one hand: this is strictly more powerful, it means any code anywhere can make a new identity and ensure it won't be affected by subsequent HIJACKs of the original function.
It also lets you avoid ordering problems:
hijack foo/ (adapt copy foo/ [print "Doesn't need an extra step!"])
You'd have to do this in multiple steps otherwise, with some kind of dummy hijacking:
/old-foo: hijack foo/ noop/
hijack foo/ (adapt old-foo/ [print "Without COPY, you have to do this"])
So it seemed superior.
BUT this turned out to be more complicated to implement, and opens a bit of a can of worms about the meaning of COPY.
What Should (Could?) "COPY an ACTION" Mean?
Let's just simplify matters a little and think about the actions which have an implementation "BLOCK!" behind them...what I've called the "Details Array".
You might imagine that making a copy that would not be subject to the same HIJACK-ings is as easy as making a copy of that array. Maybe (?) that sounds a little expensive, but, you'd imagine this isn't done too often.
However, consider something like this:
>> /g: generator [yield 1 yield 2 yield 3]
>> g
== 1
>> /g-copy: copy g/
>> g
== 2
>> g-copy
== ??? ; what do you think?
There's actually a huge problem here, in that the Details Array contains delicate state. You can't just assume duplicating that state is going to lead to a situation that won't be confused or crashy. It may contain unique pointers that one of the instances assumes it can free because it thinks it is unique.
Given this reality, the interaction between COPY of ACTION! and HIJACK was very crafty. The HIJACK only did a minor disruption to the original Details array, basically rewriting a bit of it to say "you've been hijacked" but leaving the contents of the array state in place. Copies were small stubs that could chain through to the original Details identity--and despite the fact that it had been hijacked, still run it.
But things got fairly twisted. This meant HIJACK couldn't be simple, and function copies became strange beasts that had to be conscious of the possibility that they were representations of hijackings and be conditional and that.
Further... COPY of "action" is Now COPY of FRAME!
The unification of FRAME! and action brought about a bit of a semantics problem.
COPY of an "action" now is just a mechanism of getting another FRAME! with the same parameters, that you can tweak. It doesn't imply anything about "protect against hijacking".
Hence this notion of "make new action identity that can't be hijacked" would have to be some new operator, not COPY.
We're thus talking about something called make-unhijackable-reference
.
Or... Just Say "Screw It", HIJACK Returns New Identity?
This is almost certainly the best answer.
Redoing this doesn't necessarily rule out the idea of inventing MAKE-UNHIJACKABLE-REFERENCE some day. But that would mean a hijacking would have to preserve the old implementation in a more "costly" way than it has historically, and those references would also be more costly.
Seeing it relatively clearly after having written this post, I think the added cost would be the right way to do it, if this feature were decided to actually matter to anyone.
So far the only uses of COPY of action to avoid hijackability have been done at the moment of hijacking, to re-use the implementation as part of the hijacking. The concept of "shielding references from HIJACK" for any other reason is not something that I can think of applications for.
(If you are the one exporting a function, and you think you someone might hijack it and you don't want to be subject to those hijackings, you can export an ADAPT with an empty block...or something of that sort...and then your implementation is safe, because if someone hijacks that adaptation it won't affect what the adaptation called.)