How to retrieve a function from a series of functions and call it
Asked Answered
M

4

5

I'm trying to create a dispatcher of functions in Rebol 3, so that for each string the program receives there's an associated function to be called.

For example:

handlers: make map! [
  "foo" foo-func
  "bar" bar-func
]

where foo-func and bar-func are functions:

foo-func: func [ a b ] [ print "foo" ]
bar-func: func [ a b ] [ print "bar" ]

The idea is to select the function starting from the string, so:

f: select handlers "foo"

so that executing f is the same as executing foo-func and then call f with some arguments:

f param1 param2

I tried quoting the words in the map!, or using get-words but without success.

Using a get-word! at the console, without passing through a map! it works:

>> a: func [] [ print "Hello world!" ]
>> a
Hello world!
>> b: :a
>> b
Hello world!

Any help appreciated.

Mirepoix answered 25/3, 2014 at 13:15 Comment(1)
;this one also work foo: func [] [print "hello"] do get in system/contexts/user to-word "foo"Leolaleoline
E
4

It's better to actually have the reference to the function in the map, rather than a word that refers to the function. If you store a word then you have to make sure the word is bound to an object which has a reference to that function, like this:

handlers: object [
    foo-func: func [ a b ] [ print "foo" ]
    bar-func: func [ a b ] [ print "bar" ]
]

handler-names: map [
    "foo" foo-func
    "bar" bar-func
]

apply get in handlers select handler-names name args

But if you just have a reference to the function in your map, you don't have to do the double indirect, and your code looks like this:

handlers: map reduce [
    "foo" func [ a b ] [ print "foo" ]
    "bar" func [ a b ] [ print "bar" ]
]

apply select handlers name args

Cleaner code, and more efficient too. Or if you're careful enough, like this:

handlers/(name) a b

The path method above will also work if you want the code to do nothing if there is no handler - common in cases where you have optional handlers, such as in GUIs.

You can even have more than one reference to the same function with different key names. You don't have to assign functions to words, they're just values. You can also use the path method to collect the handlers in the first place, saving a reduce.

handlers: make map! 10  ; preallocate as many entries as you expect
handlers/("foo"): func [ a b ] [ print "foo" ]
handlers/("bar"): func [ a b ] [ print "bar" ]
handlers/("baz"): select handlers "bar"  ; multiple references

That path syntax is just another way to call poke, but some prefer it. We have to put the string values in parens because of a (hopefully temporary) syntax conflict, but within those parens the string keys work. It's a faster alternative to do select or poke.

Electropositive answered 25/3, 2014 at 19:30 Comment(2)
A little more detail than @sqlab's answer, which had the best approach but didn't mention how to call the functions, and didn't show the downsides of the original approach.Electropositive
Very interesting the path syntax using directly the strings as keysMirepoix
S
5

select handlers "foo" only get the word foo-func:

f: select handlers "foo"
probe f   ;will get: foo-func

You need to get its content:

f: get f
f 1 2    ;will print "foo"

Or more compact:

f: get select handlers "foo"
Storied answered 25/3, 2014 at 14:18 Comment(0)
B
4

foo-func in your map is just an unevaluated word

>> type? select handlers "foo"
== word!

You should first create your functions and then reduce the block, you use for creating your handler map so

handlers: make map! reduce [
   "foo" :foo-func
   "bar" :bar-func
]

then you have functions inside your map

>> type? select handlers "foo"
== function!
Banky answered 25/3, 2014 at 14:4 Comment(1)
I used the :foo-func syntax but I missed completely the reduce to evaluate the block.Mirepoix
E
4

It's better to actually have the reference to the function in the map, rather than a word that refers to the function. If you store a word then you have to make sure the word is bound to an object which has a reference to that function, like this:

handlers: object [
    foo-func: func [ a b ] [ print "foo" ]
    bar-func: func [ a b ] [ print "bar" ]
]

handler-names: map [
    "foo" foo-func
    "bar" bar-func
]

apply get in handlers select handler-names name args

But if you just have a reference to the function in your map, you don't have to do the double indirect, and your code looks like this:

handlers: map reduce [
    "foo" func [ a b ] [ print "foo" ]
    "bar" func [ a b ] [ print "bar" ]
]

apply select handlers name args

Cleaner code, and more efficient too. Or if you're careful enough, like this:

handlers/(name) a b

The path method above will also work if you want the code to do nothing if there is no handler - common in cases where you have optional handlers, such as in GUIs.

You can even have more than one reference to the same function with different key names. You don't have to assign functions to words, they're just values. You can also use the path method to collect the handlers in the first place, saving a reduce.

handlers: make map! 10  ; preallocate as many entries as you expect
handlers/("foo"): func [ a b ] [ print "foo" ]
handlers/("bar"): func [ a b ] [ print "bar" ]
handlers/("baz"): select handlers "bar"  ; multiple references

That path syntax is just another way to call poke, but some prefer it. We have to put the string values in parens because of a (hopefully temporary) syntax conflict, but within those parens the string keys work. It's a faster alternative to do select or poke.

Electropositive answered 25/3, 2014 at 19:30 Comment(2)
A little more detail than @sqlab's answer, which had the best approach but didn't mention how to call the functions, and didn't show the downsides of the original approach.Electropositive
Very interesting the path syntax using directly the strings as keysMirepoix
A
2

Try: .... f: do select handlers "foo" ....

Amundsen answered 25/3, 2014 at 14:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.