PARTIAL specialization (...maybe with GET-GROUP?)


#1

Haskell has something called “currying” and it lets them write stuff like:

map (add 2) [1, 2, 3]

That results in [3, 4, 5]. So it meant the same thing as what Rebol would say as map-each x [1 2 3] [add 2 x]. Effectively, it’s as if it noticed ADD didn’t have enough parameters…but instead of erroring it assumed you wanted to create a function that would just be taking the 3 later. (Technically speaking all functions in Haskell take only take one parameter, and that’s related to the “currying”, so it’s not like it had to do something special to “notice”.)

If we imagine MAP as a function in Ren-C that did the same thing (take a function and apply it for each member of a series, return a series of the results) you’d have to do this as:

map (specialize 'add [value1: 2]) [1 2 3]

That’s wordy. It also forces you to remember the name of add’s parameters, when you may not care (and parameter names may tend to change).

So imagine we have a variant of specialization that doesn’t make you name arguments, it just kind of grabs as many as you supply…in this case one. Let’s call this PARTIAL for a moment:

map (partial 'add [2]) [1 2 3]

That’s brief, but still kind of wordy. Might it be nice if there were a way to do this that was briefer, like…say…

map :(add 2) [1 2 3]

In such a world, plain (add 2) would still be an error, since there was no colon.

One place this would be handy is because I’ve been trying to think about how to modify things like SHOVE and MATCH to operate on things other than the first argument. Right now you can say:

 >> obj: make object! [dv: enfix :divide]
 >> 8 -> obj/dv 2
 == 4

That lets you shove 8 as the left hand parameter of an enfix parameter dispatched via path. But what if you wanted it the other way around, to make it the second parameter, so you were dividing by 8. It might be nice to be able to say:

>> 8 -> :(obj/dv 2)
== 0.25

So that would “shove” the parameter to be after the 2. As another example, today’s MATCH works on the first argument:

>> match parse "aab" [some "a"]
// null

>> match parse "aaa" [some "a"]
== "aaa"

But what if instead of getting the data when the expression succeeded, you wanted the rule?

>> match :(parse "aab") [some "a"]
// null

>> match :(parse "aaa") [some "a"]
== [some "a"]

So MATCH is still grabbing the “first argument” of the function you give it on a match. You just made a partial function whose first parameter was the rule, since the data was specialized into the partial.

I had considered making this the natural syntax of match (…) … but that just feels like it is getting in the way of when you want to use GROUP! just for grouping. This feels like a different operation, and one that’s useful enough to build in.


#2

I’ll note that we already have the system making specializations for you from ordinary syntax, without having to call SPECIALIZE or other functions. For instance, GET-PATH! when you use it with a function and refinements:

>> adp: :append/dup/part
>> apd: :append/part/dup

>> block: copy [a b c]
>> adp block [d e f] 2 3
== [a b c d e f d e f]

>> block: copy [a b c]
>> apd block [d e f] 2 3
== [a b c d e d e d e]

That seems right, because outside of being an error…what else could it do? This behavior is useful and makes sense.

So having a GET-GROUP! do specialization wouldn’t be the first instance of this kind of behavior of making derived functions automatically in the evaluator.

One thing to consider is how this might be confusing with other applications of GET-GROUP!. For instance, I’d assume you’d be able to write :(first [word1 word2])…where you were intending to calculate a WORD! and then get it, ultimately acting the same as :word1.

But how would you know the difference between that and wanting to get back a function that was a full specialization of FIRST that returned word1? It can’t mean both.

Really just thinking out loud about this, so I don’t know the answer yet. But PARTIAL can be written today and would likely be useful.


#3

Sounds good to me.
Have thought about being able to define which parameter(s) you want specialized ans which one(s) should be free?


#4

If you’re going in order, I had thought that it might be nice to use BLANK! to mean leave unspecialized:

>> map partial 'divide [_ 2] [10 20 30]
== 5 10 15

>> map :(divide _ 2) [10 20 30]
== 5 10 15

But then how would you pass blanks. In PARTIAL’s case, it could have a PARTIAL/BLANK or something to turn off the most-likely-desirable default behavior. But there’s nowhere to do that with GET-GROUP!.

Still, perhaps it’s compelling enough to build in, and have people who really want to pass blanks would have to use the PARTIAL function. I dunno.

I think it would be really cool to be giving people this kind of capability without having to go through the somewhat awkward methods of Haskell (they have to flip and transform the functions to reorder their parameters so that the ones they want to specialize out come first).


#5

Exactly my thoughts.

I’d say using blanks and having a backup plan looks reasonable.