Disclaimer: I wrote function
.
The rest of the answers have done a good job of covering the standard behavior, so let's just skip that.
Let's take a look at your suggestion for a method
function to make it easier to write object-bound functions that refer to the object's words. With some tweaking it could be a useful addition to Rebol.
The Problem
Doing things the normal, right way doesn't always work. Take some code from jvargas:
o: make object! [
x: 1
y: 1
z: function [][
z: x + y
]
m: function [][
self/x: 2
self/y: 2
z: x * y
]
]
Say you need to protect the words x
and y
from being accessed from outside the object - one of the main reasons people write methods in the first place. Rebol provides a protect/hide
function that does just that. So let's use that:
protect/hide/words in o [x y]
Once you hide the words, they can't be accessed except through words that were bound to them before they were protected - we still want existing bindings to work so we can write privileged code which is allowed to access the words, but want to block outside code. So any new or "outside" code that tries to access the words will fail. Outside code, in this case, means referring to the words through a path expression, like o/x
, or through binding expressions like in o 'x
.
Unfortunately for the code above, that means that the self/x
and self/y
expressions won't work either. If they did it would be too easy to get around the restrictions like this:
do in o [self/x: "something horrible"]
That's one of the reasons why we made function/with
, so it could be used like Ladislav suggested. But function/with
won't necessarily work either because it is also meant to be able to work from the outside - it explicitly binds the function body to the object provided, which increases its power in advanced code, but doesn't help us here if the words were hidden before the method is constructed:
bar: object [
x: 10
y: 20
protect/hide/words [x y]
foo: function/with [] [
x: 304
y: 304
c: 12-Dec-2012
d: $0.50
] self
]
The call to function/with
would not reserve the x
and y
here because it wouldn't see them (they're already hidden), so those words would be local to the resulting function. To do what you want you have to use another option:
bar: object [
x: 10
y: 20
protect/hide/words [x y]
foo: function/extern [] [
x: 304
y: 304
c: 12-Dec-2012
d: $0.50
] [x y]
]
This just makes function
skip adding x
and y
to the locals, and since they were bound to the object earlier with the rest of the object's code block, before the words were hidden, they will still be bound and still work.
This is all too tricky. We may benefit from a simple alternative.
A Solution
Here's a cleaned up version of your method
function that solves a few issues:
method: func [
"Defines an object function, all set-words local except object words."
:name [set-word!] "The name of the function (modified)"
spec [block!] "Help string (opt) followed by arg words (and opt type and string)"
body [block!] "The body block of the method"
] [
unless find spec: copy/deep spec /local [append spec [
/local
]]
body: copy/deep body
append spec collect-words/deep/set/ignore body
append append copy spec 'self words-of bind? name
set name make function! reduce [spec body]
]
You use it like this:
bar: object [
x: 10
y: 20
method foo: [] [
x: 304
y: 304
c: 12-Dec-2012
d: $0.50
]
]
You might note that this looks a lot less awkward than function/with
or your method
, almost like it's syntax. You might also notice that you don't have to pass self
or a word list - so how does it work at all, given Rebol's lack of scopes?
The trick is that this function makes you put the set-word for the method name after the word method
, not before it. That is what makes it look like method syntax in other programming languages. However, for us, it turns the method name into a parameter for the method
function, and with that parameter we can get to the original object through the binding of the word, then get a list of words from that.
There are a few more factors that make this binding trick work. By naming this function method
and mentioning objects in the doc string, we pretty much ensure that this function will normally be used only in objects, or maybe modules. Objects and modules gather their words from the set-words in their code block. By making sure the name must be a set-word, this makes it likely to be bound to the object or module. We declare it with :name
to block the set-word being treated as an assignment expression, then set it explicitly within the function the way someone might naively expect it to be.
As an advantage over function/with
, method
doesn't rebind the function body to the object, it just skips the object's words like function/extern
and leaves the existing bindings. Specializing lets it be simpler, and have a little less overhead as a bonus.
The new method
also has a couple advantages over your original method
:
- The extra code in that
foreach
loop had the side effect of reserving the wordunset
in functions, and the effect of unsetting the local words for a single refinement group, when normally those words would benone
by default. Function local words are none by default on purpose, and this would make their behavior inconsistent. It's unnecessary and ill-advised. - The
self
word is special, it's not returned bywords-of
and explicitly triggers an error if you try to assign it, to help you avoid bugs. Your code loses that error, where the code infunction
was designed to preserve it. Given how bad an idea it is to accidentally overrideself
, it is better to require people to override it explicitly by declaring it in the function locals. exclude
doesn't work with blocks that have nested blocks, so your code wouldn't have worked with function specs with types declared. That's whyfunction
used thoseappend
calls in the first place.
Does this serve your purposes?