Dialecting Function Calls: New, Weird, Powerful

The advent of the CHAIN! datatype has provoked the idea of dialecting function calls themselves.

I've given examples like pointing the variable to blame for a failure...because you often don't really want an error message to implicate the location where the error itself is raised, but rather some callsite arbitrarily far up the stack.

The proposal I had for this was to change:

fail:blame ["This failure will implicate VAR at callsite"] $var

Into:

fail:$var ["This failure will implicate VAR at callsite"]

Another idea I mentioned was the concept of folding the function you want to use as a predicate into the chain. At first I suggested allowing these names to conflate with what looked like a refinement:

>> any:even? [1 3 8 7 10]
== 8

The rationalizing I used was to say "Well, in dialects we have cases where if a keyword isn't recognized then it falls back to looking up the word." That's how PARSE works... like in this historical case:

red>> begin: "[" end: "]"
== "]"

red>> parse "[aaa]" [begin copy stuff to end skip]
== false

Er, so maybe I should say "that's how PARSE doesn't work". In any case, the END keyword overrode the END variable definition. But BEGIN matched, because there's no parse keyword for BEGIN.

(Note: This is a reason why Ren-C uses <end> the tag combinator instead of END the word combinator...and other tag combinators for noun-like things.)

So maybe it's not the best plan to follow the lead there. Or as with $var, you could use another part of speech:

>> any:@even? [1 3 8 7 10]
== 8

:man_shrugging:

Is Conflation Stupid, Or Outside-The-Box Genius?

I can't really tell if allowing you to write the likes of any:even? and proceeding blissfully along outweighs the missing "conscious marker" of why EVEN? wouldn't be any refinement you can find on the HELP for ANY.

Part of me thinks "that's the way this game is played". Another part of me is thinking about how the system wouldn't know what to do with this. How would the system know how to specialize that, and put EVEN? into the :PREDICATE slot? Would any:even?/ just throw up its hands and error? For that matter, does any:@even?/ really get any easier?

I guess that aspect of "it doesn't get any easier either way" suggests we have room to experiment and see how it feels.

Actions would have to have some kind of "Interpret Chain" hook that would go from CHAIN! => FRAME!. Because this is likely to be performance sensitive, the system should offer some kind of parameterization of the default hook (e.g. "if you don't recognize anything from the spec, fetch it and throw it into parameter named XXX" or "if you see something with a certain sigil, fetch it and throw it into a parameter named YYY")

I've not been able to get dialected function calls off my brain, and the examples just keep coming to me:

>> compose:{} "some stuff {1 + 2}, (not composed)"
== "some stuff 3, (not composed)"

>> compose:[] "some stuff [1 + 2], (not composed)"
== "some stuff 3, (not composed)"

>> compose:((*)) "some stuff ((* 1 + 2)) (not composed) ((nor this))"
== "some stuff 3, (not composed) ((nor this))"

>> foo: 10

>> compose:$ [$foo foo $(1 + 2)]
== [10 foo 3]

>> compose:' ['foo foo '(1 + 2)]
== [10 foo 3]

What about rounding?

>> numerator: 10, denominator: 3

>> numerator / denominator
== 3.3333333333333335

>> round:0.1 numerator / denominator
== 3.3

; compare with `round:to numerator / denominator 0.1`

So I've been paring away the barriers (e.g. dropping GROUP! evaluations to calculate refinements...)

How Does GET Make an ACTION! From Such a Chain?

Well firstly, I'll mention that GET making an ACTION! out of a non-dialected CHAIN! has been broken for a bit. I've called this "Refinement Promotion", and there's no shortage of headaches historically.

But really this is just another way of asking: how do you MAKE FRAME! from such a Chain?

Well, let's say you do something like:

test: func [
    arg [integer!]
    :refine [tag!]
    <chain> chain
][
    probe arg
    probe refine
    probe chain
]

The idea here is you've said you're the kind of function that wants to know something about how you were invoked with a chain. Hence perhaps:

>> test 10
10
~null~  ; antiform (refine)
~null~  ; antiform (chain)

>> test:refine 10 <twenty>
10
<twenty>
test:refine

>> test:refine:(*) 10 <twenty>
10
<twenty>
test:refine:(*)

I'm assuming a few things here:

  • It's more useful to know when you weren't invoked with a chain and test it easily than when you were invoked with a word

    • Possibly false? Should it be more about wanting to know how you were invoked... so a WORD! if a CHAIN! was not used?

    • Should for-each 'item 'word [...] be willing to decay to enumerate a word as a single element? :face_with_diagonal_mouth:

  • WORD!s were treated "as is" in terms of refinement calculations, and still appear in the chain parameter

    • Should this have worked equally well if you said test:(*):refine, effectively saying words are basically reserved in function call dialects, and so you have to use other parts of speech?

    • Or should dialected function calls begin at the first non-WORD!, with no refinement processing after that point?

Or Should Dialected function Calls Get a Narrowed BLOCK!

>> test:refine 10 <twenty>
10
<twenty>
test:refine
~null~  ; antiform ("chain")

>> test:refine:(*) 10 <twenty>
10
<twenty>
[(*)]

>> test:refine:(*):etc 10 <twenty>
10
<twenty>
[(*) etc]

This makes more sense in an APPLY scenario:

apply test/ [10 refine: <twenty> chain: [(*) etc]]

So... "You Get The First Non-WORD! and Everything After" ?

Maybe that works. This does rule out:

>> any:even? [1 3 8 7 10]
== 8

But not:

>> any:@even? [1 3 8 7 10]

>> any:$even? [1 3 8 7 10]

>> any:^even? [1 3 8 7 10]

>> any:{even?} [1 3 8 7 10]

>> any:(even?) [1 3 8 7 10]

>> any:[even?] [1 3 8 7 10]

Anyway, with some of what I've been talking about with positional refinement there's:

>> any:predicate even?/ [1 3 8 7 10]
== 8

>> any:with even?/ [1 3 8 7 10]  ; or a shorter name
== 8

So if you can move the predicate before the data maybe that's enough, vs. starting a weird pattern that might be hard to generalize... saving dialected function calls for the rare cases where they really would help.

But losing the ability to take over and use WORD!s may limit interesting cases. :man_shrugging:

I had a weird thought--which resembles an old thought--but...

What if this was the meaning of any/even? e.g. what if we turned over the interstitial slash interpretation to function call dialecting as well?

The competing idea here was that it would do cascading, e.g.

>> odd?: not/even?/

>> odd? 13
== \~okay~\  ; antiform

But when you compare that idea with dialected function calls, it's not all that powerful, and doesn't come in all that handy.

If you aren't trying to make the function to store in a variable or pass as a predicate, it looks better as not even? 13... (you'd literally never write not/even? 13 just because you could)

I've also suggested folding things under a "specialization by example" ("pointfree" or "partialize") which would completely toast the limited cascading:

>> demo: pointfree [reverse append _ [a b c]]

>> demo [d e f]
== [[a b c] f e d]

In That Light, Don't Slashes Seem To Have a Higher Calling?

It would be very helpful to be able to pass functions as arguments to functions in a clear way that suggested "hey, this is a function".

I'd tried to do this at one time as e.g. all /even? [2 4 6] but any/even? [2 4 6] makes more sense.

:thinking:

There's also things like an old REDUCE* definition which is a specialization with OPT to erase nulls:

>> reduce* [1 + 2 null 100 + 200]
== [3 300]

Much more general as REDUCE/OPT:

>> reduce/opt [1 + 2 null 100 + 200]
== [3 300]

One Loss: Deferred Infix Manipulation

I had forgotten about this benefit:

(not any [a, b, c]) then [print "Without the GROUP! THEN reacts to ANY [...]"]`

not/any [a, b, c] then [print "If NOT/ANY composed the functions, this works"]

That makes the slashed form cascading the functions notably less superfluous.

But I still think the predicate-passing seems to jump out in front as much more useful day-to-day.

Especially because this particular case is covered by ANY/NOT with the predicate scheme!

One of the reasons for dropping slashes for refinements was a belief that it served better as a function call indicator.

And I think that has turned out to be very true. It's a better world, with clearer source:

obj.field  ; fetch member (ACTION! not allowed)

obj/method          ; run ACTION! (slash before: function is thing after slash
obj/method:refine   ; run ACTION! with refinement

obj.method/  ; fetch ACTION! (in "action-pack" form, for no-warning assignment)

^obj.whatever  ; meta-fetch (can be anything, given back as-is)

/word  ; dispatch function from word, error if not an ACTION!
word/  ; fetch ACTION (in "action-pack" form)

/obj.method  ; another way of running methods (probably?)

So it's worth mentioning:

Once you allow slashes in more places than the above, you're dropping the terra-firma that "if you see a slash, that means a function call...where the thing on the left is a container in which that function was found."

So here we see an anchor being lost: we no longer know by inspection that REDUCE is not an object, with a function member OPT.

Sure, we can argue it's not actually that bad. Because had it been a bare word, you would have had to have known whether REDUCE was a function or not.

If you see:

foo.reduce/opt [1 + 2 null 100 + 200]

Then you know REDUCE isn't a function. And if you see:

foo/reduce/opt [1 + 2 null 100 + 200]

Then you know REDUCE is a function.

One unsettling thing might be that to say that if you buy into this line of argument, it was never necessary to change slashes from meaning refinements in the first place.

In this view, the only "important" separation was the TUPLE! vs. PATH! change. From that, you could infer everything you need to know.

:grimacing:

I Don't Know, I Like CHAIN! And :REFINE, But Still...

Perhaps some amount of fluidity would help in terms of the "dialected function call" notion.

For example, what if that's what leading slash meant in the spec... optional parameter, that if you use a slash, it fills it in?

>> foo: func [x [block!] :y [integer!] /z [tag!]] [
       probe x
       probe y
       probe z
   ]

>> foo/<*> [a b c]
[a b c]
\~null~\  ; antiform
<*>

>> foo:y/<*> [a b c] 1020
[a b c]
1020
<*>

>> foo:y:z [a b c] 1020 <*>
[a b c]
1020
<*>

This would mean it would be compose/{} instead of compose:{} in order to make a dialected call. CHAIN! would be used for specifying the refinement by WORD!, and paths would just slip the arguments in order for the dialected call.

If you had more than one they'd just go in order:

>> foo: func [x [block!] /y [integer!] /z [tag!]] [
       probe x
       probe y
       probe z
   ]

>> foo/2/<*> [a b c]
[a b c]
2
<*>

That's Kind Of Unhinged, But Outrageously Practical

It's so useful and ergonomic that now that I see it, I kind of can't imagine not doing it this way.

Going by position in the function spec (vs. having any kind of weird magical "We pass you the chain and you process it") strips this down to its essence. A cheap, clear, fast mechanic.

We might discourage using WORD! for anything besides functions, to help avoid writing ambiguous-looking things.

But one question would be "how do we go from WORD! to 'thing word looks up to'":

switch/typecheck value [
    integer! [
         print "it was an integer"
    ]
    [series? tag!] [
         print "it was a series or tag"
    ]
    ...
]

That /PREDICATE argument is presumably ACTION! or FRAME!. So this means you'd have to distinguish between @/predicate literal (or /@predicate ?) and /predicate, where it would fetch the variable for you.

(Note: @/predicate is cheaper at time of writing--there's an optimization for the Sigil being on top of the PATH!, but not underneath it...so it costs a non-trivial amount more to have /@predicate. I actually think separating with the slash is more readable, because the letters aren't jammed up against the @ sign.)

But that gives you the problem that /PATTERN for COMPOSE would be marked as being literal. But maybe it's only literal if you use it in the PATH!, and if you use COMPOSE:PATTERN it disregards the literal marker, because you're not using the / so it handles it more like an APPLY.

:exploding_head:

(I'll reiterate that this is not entirely new. Ren-C used slashes for predicates early on, by seeing patterns like any /even? [...] and assuming that meant you wanted EVEN? as the predicate. But this is that on steriods, and the implications are huge.)

1 Like

I'm up late smoking some crack, as usual... :weary_cat:

...and it occurs to me that perhaps this could be driven by datatype. The first slashed refinement to typecheck the thing, takes it.

>> foo/2/<*> [a b c]
[a b c]
2
<*>

>> foo/<*>/2 [a b c]
[a b c]
2
<*>

There's a lot of competing interests, here. But the possibilities are vast.

Dialected function calls are one of those "going to happen soon" things, but I have to pin down all the design points.

One thing I thought of was that it could be how PRINT does interpolation... if you give it any delimiter, it assumes that's what you want to use:

>> num: 1000

>> print "Hello (num + 20), World!
== "Hello, (num + 20), World!"

>> print:() "Hello (num + 20), World!"
== "Hello 1020, World!"

>> print:{{}} "{Hello} {{num + 20}}, (World!)"
== "{Hello} 1020, (World!)"
1 Like