Shim Code for Modal Arguments

I've described a new parameter convention that I call an @arg which could be supported by the evaluator. But here is a bit of code to implement the trick in present-day Ren-C:

old-append: :append

append: function [
    series
    :nosplice [<skip> sym-word! sym-path! sym-block! sym-group!]
    value [<...> any-value!]
    /only
][
    switch type of :nosplice [
        null [old-append/(only) series take value]  ; no @... so act variadic
        sym-block! [old-append/only series as block! splicer]
        sym-group! [old-append/only series do as group! splicer]
        sym-word!
        sym-path! [old-append/only series get splicer]
   ]
]

Consequences

This leaves splicing the default:

>> append [a b c] [d e]
== [a b c d e]

But if you use an @... at the callsite it offers alternate ways of requesting /ONLY:

>> append [a b c] @[d e]
== [a b c [d e]]

The trickery though is that it has to be a literal @... at the callsite. Indirectly that doesn't apply (if it comes from a reference or expression), it will append in the typical fashion, whatever that would be:

>> item: @[d e]
== @[d e]

>> append [a b c] item
== [a b c d e]  ; or should only plain BLOCK! splice?

The callsite permits conventional expressions for the plain case (now an /ONLY behavior), written out as a full evaluation (e.g. the argument is not quoted):

>> append [a b c] reverse copy [d e]
== [a b c e d]

On the other hand, the /only cases are all going through a quoted argument...but these provide variations besides an @-block:

>> append [a b c] @item
== [a b c [d e]]

>> append [a b c] @(reverse copy item)
== [a b c [e d]]

And again, these variations are visible at the callsite; they don't accidentally sneak in to run code as /ONLY if your callsite didn't make it look that way just due to some indirect value:

>> item: @(reverse copy item)
== @(reverse copy item)

>> append [a b c] item
== [a b c reverse copy item]  ; ANY-ARRAY! splice?

If you really do want to deliberately append an @... then you can do so with quoting (which you'd have to do for a WORD! or other evaluative type that you didn't mean "do something evaluatory with")

1 Like

MODAL PARAMETERS ARE NOW DEAD!

...as far as the core is concerned. It was troubling code that contaminated the model, and it will be troubling no longer.

They led me to the solution I was looking for: a uniform literalizing @-type.

Regardless, modal parameters were based on an interesting idea: to use literal pattern recognition at the callsite to control turning refinements on and off. Above it's prototyped in usermode.

I'll put the former modal tests here in case they ever become interesting later:

[
    "Basic operational test"

    (did foo: function [@x /y] [
        reduce .voidify [x y]
    ])

    ([3 ~nulled~] = foo 3)
    ([3 #] = foo @(1 + 2))
    ([@(1 + 2) ~nulled~] = foo '@(1 + 2))

    (did item: 300)

    ([304 ~nulled~] = foo item + 4)
    ([304 #] = foo @(item + 4))
    ([@(item + 4) ~nulled~] = foo '@(item + 4))

    ([300 ~nulled~] = foo item)
    ([300 #] = foo @item)
    ([@item ~nulled~] = foo '@item)

    ([[a b] ~nulled~] = foo [a b])
    ([[a b] #] = foo @[a b])
    ([@[a b] ~nulled~] = foo '@[a b])

    (did obj: make object! [field: 1020])

    ([1020 ~nulled~] = foo obj/field)
    ([1020 #] = foo @obj/field)
    ([@obj/field ~nulled~] = foo '@obj/field)
]

[
    "Basic infix operational test"

    (did bar: enfix function [@x /y] [
        reduce .voidify [x y]
    ])

    (3 bar = [3 ~nulled~])
    (@(1 + 2) bar = [3 #])

    (did item: 300)

    ((item + 4) bar = [304 ~nulled~])
    (@(item + 4) bar = [304 #])

    (item bar = [300 ~nulled~])
    (@item bar = [300 #])

    ([a b] bar = [[a b] ~nulled~])
    (@[a b] bar = [[a b] #])

    (did obj: make object! [field: 1020])

    (obj/field bar = [1020 ~nulled~])
    (@obj/field bar = [1020 #])
]

[
    "Demodalizing specialization test"

    (did foo: function [a @x /y] [
        reduce .voidify [a x y]
    ])

    ([a @x /y] = parameters of :foo)

    ([10 20 ~nulled~] = foo 10 20)
    ([10 20 #] = foo 10 @(20))

    (did fooy: :foo/y)

    ([a x] = parameters of :fooy)
    ([10 20 #] = fooy 10 20)
    (
        'bad-parameter = (trap [
            fooy/y 10 20
        ])/id
    )
    (
        'bad-parameter = (trap [
            fooy 10 @(20)
        ])/id
    )
]

; Invisibility sensitivity
;
; Modal parameters use the unevaluated flag to inform callers that an
; argument "dissolved", so they can differentiate @(comment "hi") and @(null)
; The mechanism used is much like how <end> and <opt> are distinguished.
[
    (sensor: func [@arg [<opt> <end> any-value!] /modal] [
        reduce .try [arg modal semiquoted? 'arg]
    ] true)

    ([_ # #[false]] = sensor @(null))
    ([_ # #[true]] = sensor @())
    ([_ # #[true]] = sensor @(comment "hi"))

    ; ([_ # #[true]] = sensor @nihil)  ; !!! maybe this should work?
]