The concept of "Point Free" or "Tacit Programming" is that it's a way of relating functions to each other without mentioning their arguments.
So instead of writing something like:
append-to-data: function [value] [append data value]
You'd have some way of avoiding the redundancy of having to say "value" twice. So imagine declaring an identical function with a syntax like:
append-to-data: pointfree [append data]
That would notice you didn't provide all the parameters to append, so an implicit parameter to the new function would be added. But what if you wanted instead something like:
append-10: function [series] [append series 10]
So maybe you would have some syntax in POINTFREE for that where you put a placeholder in spots that you wanted implicitly picked up. Maybe BLANK! as a default:
append-10: pointfree [append _ 10]
(Note: pointfree may not be the best name, but it won't be taken for other reasons...and reinforces the term for Rebol programmers unused to FP concepts. There may be a nicer name.)
Writing This In Usermode In Historical Rebol Is Non-Trivial
If you think writing a reliable POINTFREE yourself would be easy in Rebol2/Red, I invite you to try. There's a lot to get right with parameter analysis and ordering refinements; beyond the average user. It doesn't get much easier if you are coding inside the system, either.
It would also be noticeably slower, using any method in near-reach. The only mechanisms would involve creating a whole new function spec, where the evaluator would have to pass parameters to that function...then start evaluating again to make a nested call...type checking all over again.
Ren-C has several tricks up its sleeve, including the duality between FRAME! and ACTION as "antiform FRAME!". This provides a convenient round-trip:
>> data: [a b c]
>> f: make frame! :append
>> f.series: data
>> apd: runs f
>> apd [d e f]
>> data
== [a b c d e f]
There's also a great convenience afforded by Ren-C's smart specialization handling. Consider:
>> parameters of :append
== [series value /part /only /dup /line]
>> apdup: :append/dup
>> parameters of :apdup
== [series value dup /part /only /line]
So if you have someone writing pointfree [append/dup ...] you don't worry...just GET the PATH!, and the function you are handed back doesn't even report DUP as a refinement. It's a normal parameter now.
There's also more unlocked by the idea of refinements naming their 0-or-1 arguments. We've seen it open doors with AUGMENT, but it helps here and elsewhere.
A Start on the Vision of POINTFREE
Below is a draft usermode implementation. It is incomplete and raises some issues. The biggest issue is that it only lets you specialize by example at one level of depth. So you can't write:
>> apde-spread: pointfree [append _ spread]
>> apde-spread [a b c] [d e]
== [a b c d e]
That would require some kind of recursive process--the call to SPREAD in the parameter slot for APPEND would have to be done using POINTFREE as well.
Moreover, there's a semantic question of when to capture variables. If you write:
>> x: 10
>> add-x: pointfree [add x]
>> x: 20
>> add-x 1000
== ???
...do you want to add the value of X as it was at the moment of specialization, and get 1010? Or to add its current value and get 1020? If you wrote out a function you'd get 1020, and I'd think the more common desire (and you should have to say pointfree compose [add (x)] to get the former intent). But as written now you get 1010, so a different mechanism is needed.
All that said, this implementation shows relatively little code, that's taking care of some rather complex acrobatics! I'd hope it wouldn't be too far beyond the reach of a novice to write.
POINTFREE* Is Lower-Level, Takes Applicand Separate From Args
This layer takes FRAME! and BLOCK! as two separate arguments. If the user does:
>> apde: pointfree* :append [_ [d e]]
Then the blank signals an unspecialized slot. So that would be like writing:
>> apde: specialize :append [value: [d e]] ; leave series unspecialized
>> apde [a b c]
== [a b c [d e]]
Note that Variadics are not supported at this time. And there's an ongoing effort to improve parameter reflection, but this is what we have at the moment.
pointfree*: func* [
"Specialize by example: https://en.wikipedia.org/wiki/Tacit_programming"
return: [action?]
frame [<unrun> frame!]
block "Invocation by example (BLANK!s are unspecialized)"
[block!]
<local> params var
][
frame: copy frame ; if given e.g. APPEND's frame, don't mutate it!
for-each param (parameters of frame) [
if tail? block [break] ; no more args, leave rest unspecialized
match [word! lit-word? get-word!] param else [
continue ; skip unused refinements
]
case [
blank? block.1 [ ; means leave unspecialized
block: next block
]
match word! param [ ; ordinary parameter
[block frame.(param)]: evaluate/next block
]
all [
match [get-word!] param ; soft literal parameter
match [group! get-word! get-tuple!] block.1
][
frame.(param): reeval block.1
block: next block
]
<default> [ ; hard literal or non-escaped soft literal
frame.(param): block.1
block: next block
]
]
]
if not tail? block [
fail/blame [
"Unused argument data at end of POINTFREE block"
] $block
]
return runs frame
]
Higher-Level POINTFREE Includes the Applicand In The BLOCK!
This higher-level version wants the name of an action at the head of the block, e.g.
pointfree [append _ [d e]]
=>
pointfree* :append [_ [d e]]
It does this by specializing out the frame on the interface of POINTFREE* and just taking a block. Then when it's called, it overwrites the dummy specialization with a frame from the first block element, and updates the block to be one step forward.
pointfree: specialize (adapt get $pointfree* [
frame: (match frame! any [ ; no SET-WORD! namecache
if match [word! path!] block.1 [
unrun get/any inside block block.1
]
]) else [
fail "POINTFREE requires FRAME! argument at head of block"
]
block: next block
])[
frame: unrun get $panic/value ; overwritten, best to be something mean
]