Question

I am trying to wrap up functions with refinements in a generic way so they can be called without the refinement. For instance, ARRAY-INITIAL size value instead of ARRAY/INITIAL size value

wrap: function [refined [path!] args [block!]] [
    function args compose [
        (refined) (args)
    ]
]

array-initial wrap 'array/initial [size value]

Not too fancy. Seems to work in general, but this has something weird if you call it using a function:

>> n: 0 array/initial 4 does [++ n] 
== [10 11 12 13]

>> n: 10 array-initial 4 does [++ n]
== [10 10 10 10]

When I source it I get this:

>> source array-initial 
array-initial: make function! [[size value][array/initial size value]]

Okay, so what's happening is that the function is being called in the wrapper and the result of the call passed...not the function. One workaround would be to use a get-word to avoid the evaluation:

>> array-initial-2: function [size value] [array/initial size :value]

>> array-initial-2: 10 array-initial-2 4 does [++ n]
[10 11 12 13]

But I was looking for a general approach. What's the best way to proxy the parameters without having this happen?

Was it helpful?

Solution

This was a fascinating exercise, gotta love SO.

Turns out you actually need a "do reduce" to wrap functions with get-word arguments ...

R3 only at the moment:

unrefine: func [
  "Return an equivalent function with the given refinements wired in."
  refined [any-path! block!] "The function, with the refinements to include."
  /local ospec spec body found p s w b r
] [
  ospec: spec-of get first refined: to lit-path! :refined
  body: append copy spec: make block! length? ospec copy/deep [do reduce []]
  append/only body/3 :refined
  parse ospec [
    set s 0 1 string! (all [s append spec s])
    any [
      set w [word! | get-word! | lit-word!] (
        append spec :w
        append body/3 either word! = type? :w [reduce ['quote to get-word! w]][w])
      set b 0 1 block! (
        all [b append/only spec copy b])
      set s 0 1 string! (
        all [s append spec copy s])
    |
      /local to end
    |
      set r refinement! (
        p: any [p tail spec]
        if not found: find next refined r [append spec r])
      set s 0 1 string! (all [not found s append spec copy s])
      any [
        set w [word! | get-word! | lit-word!] (
          either found [p: insert p :w][append spec :w]
          append body/3 either word! = type? :w [reduce ['quote to get-word! w]][w])
        set b 0 1 block! (
          all [b either found [p: insert/only p copy b][append/only spec copy b]])
        set s 0 1 string! (
          all [s either found [p: insert p copy s][append spec copy s]])
      ]
    ]
  ]
  func spec body
]

OTHER TIPS

I believe the general approach is that you do have to account for the way words are used within function arguments and as passed to functions.

wrap: func [
    'refined [path!]
    args [block!]
][
    func map-each arg args [
        either get-word? :arg [to word! arg] [:arg]
    ] compose [
        (refined) (
            map-each arg args [
                either lit-word? :arg [to get-word! arg] [:arg]
            ]
        )
    ]
]

There are two issues here—the words that define the function, and the words passed to the function. The former come in two main forms: word! for normal arguments and lit-word! for literal arguments. In our spec, if we have get-word! arguments, we want them to be normal arguments and convert to word!. When it comes to passing arguments, again we have two forms: word! to evaluate the argument and get-word! to pass the value the word points to. If our spec handles a lit-word!, we need to pass a get-word! as a word! will be passed literally.

Hopefully that all makes sense!

Anyway, how this plays out is:

wrapped-foobar: wrap foo/bar ['literal evaluated :referred]

We have our three types, the first allows you to pass values through literally—such as words without using lit-words; the second evaluates the argument before passing; and the third will pass the referred value—this form allows you to pass through functions. You end up with:

make function! [
    ['literal evaluated referred] [
        foo/bar :literal evaluated :referred
    ]
]

Now the likes of array/initial is on:

array-initial: wrap array/initial [size :value]
n: 1
array-initial 4 does [++ n]

Simply put, you (a.k.a. me) cannot accomplish this without reflecting the function arguments block that you are wrapping. There is no other way.

If a function uses the lit-word form or the get-word form of an argument, that distinction must be mirrored in your wrapper. See this answer for the explanation of what the difference is between the :foo and 'foo form of parameter:

Why doesn't Rebol 3 honor quoted function parameters that are parenthesized?

Per @rgchris's comment, what you (a.k.a. I) need to do is parse the argument block. A proper answer would contain code that does this, so people should feel free to add that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top