When I use error? and try, err need a value

2020-07-18 05:41发布

问题:

Here my function that execute cmd as a Rebol instructions :

exec-cmd: func [
        cmd [ block! ] "Rebol instructions"
        /local err
] [
        if error? err: try [
                do cmd
        ] [ print mold disarm err ]
]

When I launch the function, I've encountered the following error message :

** Script Error: err needs a value
** Where: exec-cmd
** Near: if error? err: try [
    do cmd
]

How can I avoid this message and manage the error ?

回答1:

When the Rebol default evaluator sees a sequence of a SET-WORD! followed by a "complete" expression, it will assign the result of that expression to the named word.

However, Rebol has the ability to return a special kind of "nothing" from a function called an UNSET!. For instance:

>> type? print {What "value" does a print return?}
What "value" does a print return?
== unset!

This is different from returning a NONE! value...because if you continue the chain of evaluation, the evaluator will not allow them in assignments.

>> foo: print {This isn't legal}
This isn't legal
** Script Error: foo needs a value
** Near: foo: print "This isn't legal"

Variables cannot actually "hold a value" of type UNSET!. UNSET! is just the "value type" that you will get if you try and access a variable that is not set. Regardless of the philosophical equivalence of whether there is a none value or not, the mechanical consequence is that if you want to allow an unset! value to effectively be "assigned" you have to do that "assignment" using the set function and the /any refinement:

>> set/any 'foo (print {This works...})
This works...
== unset!

But to be able to read from the value, you can't just reference it as the variable is now undefined. You need to use the corresponding get:

>> type? get/any 'foo
== unset!

Anyway, that's the background on why you're seeing this. Your cmd presumably ended with a function that returned an UNSET!, like maybe print?

Here's an example that may be illustrative:

exec-cmd: func [
    cmd [block!] "Rebol instructions"
    /local err
] [
    set/any 'result (try [do cmd])
    case [
        unset? get/any 'result [
            print "Cmd returned no result"
        ]

        function? :result [
            print ["Cmd returned a function:" newline (mold :result)]
        ]

        ;-- Rebol3 only --
        ;
        ; closure? :result [
        ;    print ["Cmd returned a closure:" newline (mold :result)]
        ; ]

        ;-- Rebol3 should be changed to match Red and not require this --
        ;
        lit-word? :result [
            print ["Cmd returned a literal word:" newline (mold :result)]
        ]

        error? result [
            print mold disarm result
        ]

        true [
            print ["Cmd returned result of type" (mold type? result)]
            print ["The value was:" newline (mold result)]
        ]
    ]
]

Notice that once you've already handled the case where result might be unset, you don't have to use get/any and can just do normal access.

There is a foundational issue in the way the interpreter works, that if a word is bound to a FUNCTION! value (also the CLOSURE! values in Rebol3) then referencing that word invokes the related code. To work around this, if you know you're in a situation where a word may hold a such a value you can use GET or the analogue of a SET-WORD! known as a GET-WORD!. These are generally considered "ugly" so it's best if you can isolate the part of the code that needs to test for such an edge case and not wind up putting colons in front of things you don't need to!

What has been deemed a design flaw is something called "lit-word decay". This necessitates the use of a GET-WORD! in Rebol2 if you have an actual literal word in your hand. In that case, your program won't crash, it just won't give you what you expect. It's explained here...it has already been changed in Red so it's certain to change for Rebol3.

Also, the concept of errors being "armed" and needing to be "disarmed" to be inspected has been eliminated in Rebol3. That doesn't affect the error? test in Rebol2 such that you'd need to use a GET-WORD!, but affected just about everything else you could do with them.

All right. I think I've covered all the cases here, but someone will correct me if I haven't!


(Note: if you're curious how to make your own function that returns an UNSET! like print does, just use exit instead of return)

>> nothing: func [value] [exit]

>> type? nothing 1020
== unset!


回答2:

Use set/any and get/any to handle values that regular assignment and evaluation can't.

if error? set/any 'err try [
    do cmd
] [ print mold disarm get/any 'err ]

Once the error is disarmed you can handle it normally.