patternMinor
Find arity of a rebol function
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
Let's take it for a spin:
Questions I have:
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
== 0Questions 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
copythe parameter block fromflike 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:
(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:
@HostileFork's answer already mentions WORDS-OF, which is a convenient way to get only the core parts of a function spec:
Building on that, the core idea is to use FIND with a datatype, to position to the first refinement! argument:
Great! But what about functions which have no refinements at all, like
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:
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:
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à:
I hope you enjoyed the ride :)
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!
== noneTo 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!
== 3As 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!
== 2I 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!
== noneContext
StackExchange Code Review Q#85272, answer score: 7
Revisions (0)
No revisions yet.