Why does return/redo evaluate result functions in the calling context, but block results are not evaluated?

后端 未结 2 1908
时光取名叫无心
时光取名叫无心 2020-12-31 22:14

Last night I learned about the /redo option for when you return from a function. It lets you return another function, which is then invoked at the ca

2条回答
  •  佛祖请我去吃肉
    2020-12-31 22:51

    While the question originally asked why return/redo did not evaluate blocks, there were also formulations like: "is cool because you can do things like tail call optimization", "[can write] a wrapper for the return functionality", "it seems to be getting more and more useful the more we think about it".

    I do not think these are true. My first example demonstrates a case where return/redo can really be used, an example being in the "area of expertise" of return/redo, so to speak. It is a variadic sum function called sumn:

    use [result collect process] [
        collect: func [:value [any-type!]] [
            unless value? 'value [return process result]
            append/only result :value
            return/redo :collect
        ]
        process: func [block [block!] /local result] [
            result: 0
            foreach value reduce block [result: result + value]
            result
        ]
        sumn: func [] [
            result: copy []
            return/redo :collect
        ]
    ]
    

    This is the usage example:

    >> sumn 1 * 2 2 * 3 4
    == 12
    

    Variadic functions taking "unlimited number" of arguments are not as useful in Rebol as it may look at the first sight. For example, if we wanted to use the sumn function in a small script, we would have to wrap it into a paren to indicate where it should stop collecting arguments:

    result: (sumn 1 * 2 2 * 3 4)
    print result
    

    This is not any better than using a more standard (non-variadic) alternative called e.g. block-sum and taking just one argument, a block. The usage would be like

    result: block-sum [1 * 2 2 * 3 4]
    print result
    

    Of course, if the function can somehow detect what is its last argument without needing enclosing paren, we really gain something. In this case we could use the #[unset!] value as the sumn stopping argument, but that does not spare typing either:

    result: sumn 1 * 2 2 * 3 4 #[unset!]
    print result
    

    Seeing the example of a return wrapper I would say that return/redo is not well suited for return wrappers, return wrappers being outside of its area of expertise. To demonstrate that, here is a return wrapper written in Rebol 2 that actually is outside of return/redo's area of expertise:

    myreturn: func [
        {my RETURN wrapper returning the string "indefinite" instead of #[unset!]}
        ; the [throw] attribute makes this function a RETURN wrapper in R2:
        [throw]
        value [any-type!] {the value to return}
    ] [
        either value? 'value [return :value] [return "indefinite"]
    ]
    

    Testing in R2:

    >> do does [return #[unset!]]
    >> do does [myreturn #[unset!]]
    == "indefinite"
    >> do does [return 1]
    == 1
    >> do does [myreturn 1]
    == 1
    >> do does [return 2 3]
    == 2
    >> do does [myreturn 2 3]
    == 2
    

    Also, I do not think it is true that return/redo helps with tail call optimizations. There are examples how tail calls can be implemented without using return/redo at the www.rebol.org site. As said, return/redo was tailor-made to support implementation of variadic functions and it is not flexible enough for other purposes as far as argument passing is concerned.

提交回复
热议问题