Frage

The low-level primitives in Rebol for functions and closures are FUNC and CLOS. Without explicitly telling the FUNC or CLOS to make something local, then assignments will not be local.

x: 10
y: 20

foo: func [/local x] [
   x: 304
   y: 304
]

foo

print [{x is} x {and} {y is} y]

This will output:

x is 10 and y is 304

The higher-level routines FUNCTION and CLOSURE are written as Rebol code in the default library. They scan the body for symbols of category SET-WORD (such as x: and y:). Then they automatically generate an augmented function specification which adds them as /LOCAL:

x: 10
y: 20

foo: function [] [
   x: 304
   y: 304
]

foo

print [{x is} x {and} {y is} y]

This will output:

x is 10 and y is 20

That's better almost all of the time, so it's good that these get the prettier names. Yet how can you use FUNCTION as an object member?

bar: object [
    x: 10
    y: 20

    foo: function [] [
        x: 304
        y: 304
        c: 12-Dec-2012
        d: $0.50
    ]
]

That won't behave like in other languages where within an object, it's assumed that the members are not hidden by local variables by default. What is someone to do if they want foo to act like a FUNC on any words set in the object, but a FUNCTION for words that are not?

The only thing I thought of was to pass self into a variant of the code for FUNCTION, something like:

method: func [
    me [object!] {always the parameter "self"?}
    spec [block!]
    body [block!]
] [
    unless find spec: copy/deep spec /local [append spec [
         /local
]]
    body: copy/deep body
    append spec exclude collect-words/deep/set/ignore body words-of me spec
    foreach l next find spec /local [
    if refinement? l [
        break
    ]
    insert body to-lit-word l
        insert body 'unset
    ]
    make function! reduce [spec body]
]

But then you would have to write foo: method self [] [...] which is wordy (assuming this approach is even legitimate).

Is there any trick to get past passing in self, or some other idiom for supporting this desire? Or does everyone just use FUNC as object members?

War es hilfreich?

Lösung 2

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 word unset in functions, and the effect of unsetting the local words for a single refinement group, when normally those words would be none 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 by words-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 in function was designed to preserve it. Given how bad an idea it is to accidentally override self, 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 why function used those append calls in the first place.

Does this serve your purposes?

Andere Tipps

The behavior described results from the dynamic scope used in rebol. The proposed definition for :method infers the locals from the body of the function, while allowing access to the instance variables of the object without any declaration effort from the programmer. This type of leaky abstraction is dangerous in the presence of dynamic scoping. For example:

The programmer writes this initial version:

o: make object! [
   x: 1
   y: 1

   m: method [][
      x: 2
      y: 2
      z: x * y
   ]
]

Many revisions later, another programmer decides to revise the code to this:

o: make object! [
   x: 1
   y: 1

   z: method [][
      z: x + y
   ]

   m: method [][
      x: 2
      y: 2
      z: x * y
   ]
]

Depending on the execution path, the revised code could give different results. An invocation of the o/m method will override the o/z method. Thus the proposed implementation introduces an element of surprise.

By saving the programmer the effort to express its intent clearly, the code has become brittle. You can be explicit that you want a member of the object simply by using self when that is what you mean:

o: make object! [
   x: 1
   y: 1

   z: function [][
      z: x + y
   ]

   m: function [][
      self/x: 2
      self/y: 2
      z: x * y
   ]
]

You can then use FUNCTION and CLOSURE and it is readable and explicit.

This works at present, but it probably is not exactly what you wished:

bar: object [
    x: 10
    y: 20

    foo: function/with [] [
        x: 304
        y: 304
        c: 12-Dec-2012
        d: $0.50
    ] self
]
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top