HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Find arity of a rebol function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
functionrebolarityfind

Problem

I needed a way to find the number of arguments a function or operation in Rebol accepts (the functions arity). I could not find any direct support for this in the language, so I made my own arity function:

arity: func [
    "Returns the arity of a function, disregarding refinements."
    :f [function! op! native! action!]
    /local spec result
] [
    result: 0
    spec: copy third :f
    loop length? spec [
        switch type?/word first spec [
            word! [ result: result + 1 ]
            refinement! [ break ]
        ]   
        spec: next spec
    ]   
    result
]


Let's take it for a spin:

REBOL/Core 2.7.8.4.2 (2-Jan-2011)
>> do load %arity.reb
>> help arity
USAGE:
    ARITY :f 

DESCRIPTION:
     Returns the arity of a function, disregarding refinements.
     ARITY is a function value.

ARGUMENTS:
     f -- (Type: function op native action)
>> arity print
== 1
>> arity +
== 2
>> arity either
== 3
>> arity quit
== 0


Questions I have:

  • Are there really no easier, built in way of getting the arity?



  • Am I covering everything (all function types) by specifying [function! op! native! action!]?



  • Are there any corner cases where this function will give the wrong answer?



  • Does it make sense to copy the parameter block from f like I do here?



  • Finally: Can you suggest any improvements to my code?

Solution

Finally: Can you suggest any improvements to my code?

Fair warning ahead: I've only tried the following with Rebol 3. That said: going into a different direction than the improvements suggested by @HostileFork, here's an alternative approach:

arity: func [
    "Returns the arity of a function, disregarding refinements."
    f [any-function!]
] [
    -1 + index? find join words-of :f /sentinel refinement!
]


(No get-/lit-argument tricks here, sorry. Also refer to @HostileFork's answer for that particular aspect.)

The Gory Details

For those not that familiar with Rebol, let's dissect that step by step. First, a fully parenthesised variant of the core expression:

-1 + (index? (find (join (words-of :f) /sentinel) refinement!)


@HostileFork's answer already mentions WORDS-OF, which is a convenient way to get only the core parts of a function spec:

>> words-of :append
== [series value /part length /only /dup count]

>> words-of :+
== [value1 value2]


Building on that, the core idea is to use FIND with a datatype, to position to the first refinement! argument:

>> find words-of :append refinement!
== [/part length /only /dup count]


Great! But what about functions which have no refinements at all, like + shown above?

>> find words-of :+ refinement!
== none


To counter that, we simply add a sentinel value at the end, so that we can ensure that there'll always be a refinement! present:

>> join words-of :+ /sentinel
== [value1 value2 /sentinel]

>> find join words-of :+ /sentinel refinement!
== [/sentinel]


For our final step, we utilise the fact that Rebol blocks are "cursor-like". FIND does not return a copy of the original block with just the remaining elements after and including the match, FIND returns a new block "cursor" which is positioned right after the match within the original data. We can use INDEX? to ask this "cursor" its current position:

>> index? find join words-of :+ /sentinel refinement!
== 3


As we can see, this expression using INDEX? gives us the (1-based) position of the first refinement. Subtract 1 from that, and we have the count of non-refinement arguments. (If we'd have zero-based functions already, we could get rid of that final step.)

Et voilà:

>> -1 + index? find join words-of :+ /sentinel refinement!
== 2


I hope you enjoyed the ride :)

Code Snippets

arity: func [
    "Returns the arity of a function, disregarding refinements."
    f [any-function!]
] [
    -1 + index? find join words-of :f /sentinel refinement!
]
-1 + (index? (find (join (words-of :f) /sentinel) refinement!)
>> words-of :append
== [series value /part length /only /dup count]

>> words-of :+
== [value1 value2]
>> find words-of :append refinement!
== [/part length /only /dup count]
>> find words-of :+ refinement!
== none

Context

StackExchange Code Review Q#85272, answer score: 7

Revisions (0)

No revisions yet.