Shim Code for Modal Arguments

UPDATE: 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.

The elegant solution of isotopic blocks is now solving what they were trying to do.

Regardless, modal parameters were based on an interesting idea: to use literal pattern recognition at the callsite to control turning refinements on and off.


Modal Parameters are an idea 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 else ["; null"]]
     print ["b:" mold b]
     print ["mode-b:" mold mode-b else ["; null"]]
 ]

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: ; null
 b: 30
 mode-b: ; null

 >> demo @(1 + 2) 10 + 20
 a: 3
 mode-a: #
 b: 30
 mode-b: ; null

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

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

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.

Proof-Of-Concept Implementation In Usermode

Here's a sample of how APPEND might splice blocks by default, but suppress with @.

It was designed to operate on top of the Redbol APPEND/ONLY model where splices are default.

append: function [
    series
    'nosplice [<skip> the-word! the-path! the-block! the-group!]
    value [<...> any-value!]
    /only
][
    switch type of nosplice [
        null [redbol.append series take value] ; no @... act variadic
        the-block! [redbol.append/only series as block! splicer]
        the-group! [redbol.append/only series do as group! splicer]
        the-word! [redbol.append/only series get splicer]
        the-path! [redbol.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

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 @none)  ; !!! maybe this should work?
]