As I bring the Rebmu dialect up to date I'll try and talk about any teachable moments that it provides.
Let's look at one of the earliest weird functions, REBMU-WRAP:
rebmu-wrap: function [refined [path!] args [block!]] [
func args compose [
(refined) (args)
]
]
It was designed to make it (slightly) easier to define something like APO
as a synonym for APPEND/ONLY
.
APO: rebmu-wrap 'append/only [series value]
So that turns into effectively:
APO: func [series value] [
append/only series value
]
Basically this was an incredibly weak way to save a few characters while making a partial specialization. Some of its weaknesses:
-
You still have to specify the number and names of arguments the specialization takes.
-
If you make a mistake at the time of definition (like mentioning an unavailable refinement) you don't get the error until the call.
-
The other refinements are missing from the wrapped function, e.g. you can't invoke
apo/part
orapo/dup
. -
You don't inherit anything about the original function's parameter conventions; e.g. if an argument were quoted you'd get the parameter name word you passed for it.
-
Performance-wise you generate a whole new function call...path evaluation...argument evaluation...
-
Your new function doesn't have any HELP... etc. etc.
How To Make This Better in Classical "Redbol"?
Let's put yourself in the mind of someone thinking about how I was at that time, who wanted to use the same strategy but merely address (1) and (2). So to be able to write the slightly shorter hand:
APO: rebmu-wrap 'append/only
APD: rebmu-wrap 'append/dup
APDP: rebmu-wrap 'append/dup/part
APPD: rebmu-wrap 'append/part/dup ; ...etc.
This version of REBMU-WRAP would need to analyze the spec, presumably with WORDS-OF:
r3-alpha>> words-of :append
== [series value /part length /only /dup count]
So you might try something like the following nightmare...which took me at least an hour to write and debug! (probably more like two, because of the glitch of trying to mutate the spec in place and needing to notice when the insertion position matches the position a refinement argument is being moved from):
rebmu-wrap: func [refined [path!] /local spec body pos insert-pos item] [
if not any-function? (action: get first refined) [
do make error! "first element of path must refer to a function"
]
spec: words-of :action
body: reduce [refined]
insert-pos: spec ; find where refinement args should be inserted
while [all [not tail? insert-pos not refinement? insert-pos/1]][
append body insert-pos/1
insert-pos: next insert-pos
]
foreach refinement-name (next refined) [
if not pos: find spec to-refinement refinement-name [
do make error! "missing or duplicate refinement specified"
]
take pos ; drop refinement from spec
while [all [not tail? pos not refinement? pos/1 pos != insert-pos]] [
insert-pos: insert insert-pos item: take pos
append body item
]
]
func ?? spec ?? body
]
If you try this in R3-Alpha, yes it does indeed work for (1) and (2). I put in the probes so you can see the spec and body of the new function:
r3-alpha>> apo: rebmu-wrap 'append/only
spec: [series value /part length /dup count]
body: [append/only series value]
r3-alpha> apd: rebmu-wrap 'append/dup
spec: [series value count /part length /only]
body: [append/dup series value count]
r3-alpha>> apdp: rebmu-wrap 'append/dup/part
spec: [series value count length /only]
body: [append/dup/part series value count]
r3-alpha>> appd: rebmu-wrap 'append/part/dup
spec: [series value count length /only]
body: [append/part/dup series value count]
It gets a head-start on (3) by leaving the refinements that haven't been partially specialized on the spec, but they're ignored in the call.
That's A Lot Of Work for Something That Still Sucks!
When you think of everything it entails to get (3) and (4), you are going to either need to COMPOSE your invocation on each call -or- change methodology to use APPLY. But even if you are aware of APPLY it doesn't make this a trivial exercise, and doesn't help with (5) or (6).
...and this is why I never even tried to write such things (before)... instead trying to add core interpreter features for partial specialization.
The Happy New World of Partial Specialization
In today's Ren-C, all you have to do is get a function as a path, and you have (1) (2) (3) (4) (5).
ren-c>> ap: :append
== #[action! {ap} [series value /part /dup /line /only]]
ren-c>> ap [a b c] [d e]
== [a b c d e]
ren-c>> apo: :append/only
== #[action! {apo} [series value /part /dup /line]]
ren-c>> apo [a b c] [d e]
== [a b c [d e]]
ren-c>> apo/dup [a b c] [d e] 2 ; e.g. demonstrating (3)
== [a b c [d e] [d e]]
The new function call is just as efficient as the original (you might think of it moreso since there's no path processing, e.g. apo is faster than invoking ap/only!)
You don't get the HELP inherited automatically (it has an associated cost), but there is a function that will do it if you want to... you say inherit-meta :apo :append and then help apo will have the help strings copied into its meta information.
So that's some assistance with (6), but do remember that it's not a perfect world when talking about how to automatically write meaningful adjusted help. If any parameter descriptions refer to the argument you specialized out, it's no longer on the interface, so they may not make sense!
(Note: Now that refinements are their own arguments, you can't tell by parameter list alone if a refinement is a 0 or 1 arg case. It's determined by the typeset: if the typeset is empty--no types accepted--it is a 0 arg refinement. Something to ponder if this can be done better, but see all the other nice things...like caching the label of the function name for stack traces, etc.)
So long, REBMU-WRAP...
...but I wanted to write this up just to show a good example of how early experiences playing with Rebmu led me to question Rebol, and want to shore up its abilities.
"Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy."