5

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?

  • @endo64 You [added a copy](http://stackoverflow.com/revisions/22892499/3)... that's the Rebol2 function. In Rebol3 the decision was made to eliminate that form of FUNCTION... it's now what was once known as FUNCT. More user-friendly that way (and Red-compatible). My suggestion is we basically just try to axe all the FUNCTION uses in Rebol2 code samples and then switch all the FUNCT to FUNCTION in Rebol3. – HostileFork says dont trust SE Apr 06 '14 at 18:07
  • Oh right, I forgot that. – endo64 Apr 07 '14 at 14:57

3 Answers3

1

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]
rgchris
  • 3,698
  • 19
  • 19
  • Okay, great. That fixes a bug someone was asking me about on GitHub. :-) I do feel it's a common enough desire that it would be nice if the function specification dialect could let you do some kind of specialization more easily. I'd rather do this with reflection but maybe someone could add another answer that adjusts for that if they're bored... – HostileFork says dont trust SE Apr 06 '14 at 18:12
  • Hm... one issue here is that the prototype doesn't match the wrapped function. So for instance, the wrapper can end up with a quoted arg when the original didn't have it *(it's not `/initial value`... but the wrapped array-initial takes `'value`)*. Is there a way to make the wrapped prototype line up with the original? – HostileFork says dont trust SE Apr 06 '14 at 18:45
  • Oh, that's my bad. You can [parse a function arguments block](http://stackoverflow.com/questions/17707073/how-to-parse-a-functions-argument-block) to automatically generate your wrapped function—up to and including type constraints and docs—that way you only need to supply the path and the rest is filled in for you. But that's definitely more work. – rgchris Apr 06 '14 at 19:43
1

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
]
MarkI
  • 347
  • 2
  • 8
0

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.

Community
  • 1
  • 1