New Parameter Convention Concept: Modal Arguments (or "the @arg")

This is a vision statement for using the new @word, @pa/th, @[bl o ck] and @(gr o up) datatypes for an interesting purpose.

The idea is to have a way to mark an argument conveniently as controlling a refinement--without mentioning that refinement at the callsite, or needing a specialization of the function dispatched through a different name.

Perhaps it's easiest to see by example:

 demo: function [@a /mode-a @b /mode-b] [
     print ["a:" mold a]
     print ["mode-a:" mold mode-a]
     print ["b:" mold b]
     print ["mode-b:" mold mode-b]
 ]

Using literal (non-quoted) @-arguments will automatically turn on the associated mode refinement. The argument itself will be the appropriate value for the non-@ version of the argument. (WORD! and PATH! will fetch, GROUP! will evaluate, BLOCK! will be unevaluated).

 >> demo 1 + 2 10 + 20
 a: 3
 mode-a: _
 b: 30
 mode-b: _

 >> demo @(1 + 2) 10 + 20
 a: 3
 mode-a: /mode-a
 b: 30
 mode-b: _

 >> foo: "foo" | demo @[x y z] @foo
 a: [x y z]
 mode-a: /mode-a
 b: "foo"
 mode-b: /mode-b

 >> foo: [10 @bar] | demo '@[x y z] second foo
 a: @[x y z]
 mode-a: _
 b: @bar 
 mode-b: _

The refinements are available to invoke independently as normal. This means that from a specialization or APPLY standpoint the parameter convention would be as-is.

This is provably feasible as it can be implemented today in a somewhat awkward manner.

The motivation is to get rid of /ONLY, and instead have a very brief notation for differentiating the treatment of a block as either one or multiple things...one of the key issues in Rebol. Having to pay more than a single character for this runs against the grain of the language principles.

>> map-each x [1 2 3] [
    reduce [x x + 1]
]
== [[1 2] [2 3] [3 4]]

>> map-each x [1 2 3] @[
    reduce [x x + 1]
]
== [1 2 2 3 3 4]

If we can avoid it, I think that none of us want to see MAP-EACH/SPLICE or MAP-EACH/ONLY spreading throughout the code. Attaching the notation of the "special" argument to the argument itself--instead of warping the name of the function--has value. Otherwise you'd have to be constantly making specializations to get MAP-EACH*, etc. It doesn't look as good, doesn't generalize to multiple arguments, and means making another word each time.

Being able to solve it with a nice generic part that is free for other dialect purposes seems a good idea. Casting it in terms of an interface that still works with APPLY, FRAME!s, and specialization seems a good idea.

The proposal is based in something actually practical in the evaluator, as the part is kind of designed for it, and I have high confidence this can be accomplished.

1 Like

Hope we get some good input here. I prefer the principle of splicing with a single character/token. /ONLY and /SPLICE don't make the code more clean or literate, in my opinion.

1 Like

>> foo: "foo" | demo @[x y z] @item

I think @item should be @foo ...

Yup, right, fixed.

The main question I have at the moment is how to tie the refinement to the argument. Forcing them to be sequential in the frame feels imperfect but may be best.

Rebol is already a bit of a slame to sequentiality for normal (non-refinement) arguments, it's not like this would be introducing a new aspect on that.

Mechanical question: should @foo when used as a modal parameter act like a WORD! or a GET-WORD!? Or a bit of both?

Acting like a GET-WORD! would mean not running functions and being willing to give NULL back if a variable is unset.

 x: null
 append data @x  ; adds nothing with no error if acts a GET-WORD!

 f: func [] [return "zero arity function"]
 append data @f  ; adds function value (doesn't call) if GET-WORD!

Supporting the idea that it should be a GET-WORD! would be that if you wanted a function call, you could say @(f), which looks better than @(:f)...which you'd have to use if it were biased the other way.

Not wanting to accidentally use an unset variable might suggest requiring @(:x) if it can be null, however. It seems too risky to have append data value error if you have a null, but then append data @value suddenly silently allows nulls...when all you meant to say was different was "treat as multiple items if a block!"

I've committed a starting implementation of modal parameters, and went ahead with the MAP-EACH example to get people thinking...

If this goes forward, it seems to me the best plan for transition would be to require every APPEND where the thing-being-appended is an ANY-ARRAY! to either have an /ONLY -or- an @-parameter (or a /SPLICE if you want, but that's ugly). Basically, we go through a period of time where "naked" append is not allowed for array values...which will be irritating, but then one day all the /ONLYs will become plain APPEND.

I was wondering what the implications would be for path selection, since there's nowhere obvious to put a refinement there. As it happens, it appears historically the /ONLY was presumed...in Rebol2 and R3-Alpha:

rebol2>> data: [[a b] c]
== [[a b] c]

rebol2>> item: [a b]
== [a b]

rebol2>> data/(item)
== c

rebol2>> select data item
== none

rebol2>> select/only data item
== c

So no trouble there.

!!! We need to be strategic about which codebases are held back as "Redbol" and which are committed to going future-y. The hope is that the core will be able to serve both purposes, and that you can mix and match modules written to Redbol conventions with modern Ren-C. I've already decided to hold my blog back on Redbol for a while, Atronix's aim in a ZOE port would be Redbol--at least for starters...and people should decide on a codebase-by-codebase basis. So, let's strategize. !!!

1 Like

Ia there a reason not to have @234, @"text", ... apart from it being a no-op?
With modal arguments it would be nice to have these.

The reasoning is that @xxx is categorized as an ANY-WORD! and not an ANY-STRING! (with ISSUE! returned to an ANY-STRING!). An operating rule is that all legal ANY-WORD! can be transformed to each other. So if @234 was valid there'd have to be a way to have a plain WORD! form, a SET-WORD! form, etc. etc.

I used to subscribe to the idea that the way to do this was with "construction syntax", something like #[set-word! "234"] (or at least no worse than that). But with time I felt like it was better to avoid this, and move to things like [234]: ... the question is where the sweet spot is. We've had a lot of unification with UTF-8 everywhere, e.g. that every ANY-WORD! can be aliased as an ANY-STRING! and use the same bytes of memory for the data...but not necessarily vice versa; and I'm feeling like mechanically it starts getting messy if we try to push that further.

Here I write about the shift in mindset, so if you want to discuss it add your comments there: ANY-WORD! and ANY-STRING!: The Limits of Unification