ARITY-OF: Then and Now


#1

I’m going to have to start getting kind of brutal cutting dicey things out of the system for Beta/One. So when I ran across the ARITY-OF routine and all its disclaimers and concerns, I thought “oh no, well this simple-seeming thing is more trouble than it’s worth, to explain and document…”

Classical Rebol doesn’t have it. People who want to know the arity of a function have to get WORDS OF the function, then count up to the first refinement, and then say “done”.

But you’re not done, because if you’re processing source you might have seen something like APPEND/DUP/PART. How do you ask for the arity of that, without calling it? Massive unsolved problem.

But, wait a minute…

It was unsolved back then. But there’s a lot to be said for doing things right. :man_mechanic:

>> parameters of :append
== [series value /part limit /only /dup count /line]

>> parameters of :append/dup
== [series value count /part limit /only /line]

>> parameters of :append/part
== [series value limit /only /dup count /line]

>> parameters of :append/part/dup
== [series value limit count /only /line]

>> parameters of :append/dup/part
== [series value count limit /only /line]

…and so all we have to write is…

arity-of: function [
    "Get the number of fixed arguments to an ACTION! (if not variadic)"
    return: [integer!]
    action [action!]
][
    if variadic? :action [
        fail "ARITY-OF cannot give an answer for variadic actions"
    ]
    arity: 0
    for-each p parameters of :action [
        if refinement? :p [break]
        arity: arity + 1
    ]
    return arity
]

Exercise for the reader: try doing this in Rebol2/R3-Alpha/Red :slight_smile:

>> map-each x [
          append append/dup append/part append/dup/part append/part/dup
     ][
          arity-of get x
    ]
== [2 3 3 4 4]

#2

But I’m still going to cut ARITY-OF from Core

Here’s why:

 >> foo: func ['x 'y] [...]
 >> arity-of :foo
 == 2

 >> bar: func [x y] [...]
 >> arity-of :bar
 == 2

What good is this number going to do anyone? You can’t use it to know how many things would be taken spliced after something, you need to know if the arguments are quoted. Tightness and non-tightness affects things too.

You have to look at the specific PARAMETERS OF and understand what part of that is important to what you are doing. We’re not necessarily doing anyone any favors by trying to digest and oversimplify what’s in there.

It’s still nice to point out how today, you can start from a PATH! and get to an ACTION!, and from there get to a PARAMETERS OF that is meaningful. It’s a huge improvement. But now that you have that information, you need to integrate it with what you’re trying to accomplish with your reflection.

Some other little routines are getting cut too…

These guys have the same problem. Tight or non-tight parameter conventions can make two seemingly INFIX? functions be very different. So what did you actionably learn from that oversimplification?

nfix?: function [
    n [integer!]
    name [text!]
    source [any-word! any-path!]
][
    case [
        not enfixed? source [false]
        equal? n arity: arity-of source [true]
        n < arity [
            ; If the queried arity is lower than the arity of the function,
            ; assume it's ok...e.g. PREFIX? callers know INFIX? exists (but
            ; we don't assume INFIX? callers know PREFIX?/ENDFIX? exist)
            false
        ]
    ] else [
        fail [
            name "used on enfixed function with arity" arity
                |
            "Use ENFIXED? for generalized (tricky) testing"
        ]
    ]
]

postfix?: redescribe [
    {TRUE if an arity 1 function is SET/ENFIX to act as postfix.}
](
    specialize :nfix? [n: 1 | name: "POSTFIX?"]
)

infix?: redescribe [
    {TRUE if an arity 2 function is SET/ENFIX to act as infix.}
](
    specialize :nfix? [n: 2 | name: "INFIX?"]
)

I don’t feel bad cutting these, so long as it’s easy enough to write what you mean for your purpose.