A "supercharged" Map function.
[1/16] from: brett:codeconscious at: 22-Jul-2003 14:25
Hi List,
I was thinking about the way functions collect their arguments from scripts
and then my attention turned to the Map function that many of us have used
or created, and then I thought how nice it would be if Map could apply
functions with multiple arguments. Then I went over-the-top. I wouldn't be
suprised if someone has already got something like this floating around but
anyware here it is. See the examples below for what it does.
; Carl Sassenrath's nargs function.
nargs: func [
{The number of the function arguments}
f [any-function!]
] [-1 + index? any [find first :f refinement! tail first :f]]
map: func [
[catch]
"Map a function to a series (unsets filtered by default)."
mapfunc [any-function! block! none!]
"Function or function body block (use VALUE as argument)."
series [series! none!] "Series to map."
/only "Inserts into result using Only refinement."
/filter filterfunc [any-function! block!]
"Single argument function or body (use VALUE as argument)."
/local num-arg result emit
] [
; Check arguments for none!
if any [none? :mapfunc none? series] [return none]
if block? :mapfunc [
mapfunc: func [value [any-type!]] mapfunc]
if not filter [
filterfunc: [not unset? get/any 'value]]
if block? :filterfunc [
filterfunc: func [value [any-type!]] filterfunc]
num-arg: nargs :mapfunc
result: make type? series length? series
emit: func [value [any-type!]] compose/deep [
if filterfunc get/any 'value [
(pick [insert/only insert] found? only)
tail result get/any 'value
]
]
while [not tail? series] [
emit do compose [mapfunc (copy/part series num-arg)]
series: skip series num-arg
]
result
]
comment [
; Shortcut for specifying mapping function.
map [value] [1 2 3 4]
; Passing functions as the mapping function.
map :add [1 2 3 4]
map func [a b] [a / b] [1 2 3 4]
; Default handling of block results.
map [reduce [value value * 10]] [1 2 3 4]
; Blocks retained.
map/only [reduce [value value * 10]] [1 2 3 4]
; These return none.
map none [1 2 3 4]
map [value] none
map none none
; These return empty blocks.
map [] []
map [] [1 2 3 4]
map [value] []
; Custom filter of function results.
map/filter [value] [1 2 3 4] [value >= 3]
; Default unset! handling.
map [print [value]] [1 2 3 4]
; Default unset! handling overriden.
map/filter [print [value]] [1 2 3 4] [true]
; Flattening a tree.
sample-tree: [1 [2 [] 3 [4 []]] 5 []]
f: func [label subtree] [
append reduce [label] map :f subtree
]
map :f sample-tree
; Converts output from Parse-xml to a tree of element names.
elt: func [name att content] [
reduce [
name
map/filter [
if block? value [map :elt value]
] content [not none? value]
]
]
map :elt first third parse-xml xml
]
Regards,
Brett.
---
Website: http://www.codeconscious.com
Rebsite: http://www.codeconscious.com/index.r
[2/16] from: greggirwin:mindspring at: 21-Jul-2003 22:58
Thanks Brett!
I'll have to play with this. I have a bunch of not-so-very-nice,
not-so-general MAP-ish things that will probably benefit from...being
thrown away. :)
-- Gregg
[3/16] from: brett:codeconscious at: 22-Jul-2003 19:32
Don't throw away your MAP-ish things just yet - you may not be expecting
that my version evaluates the series elements, which may or may not please
you :^)
Another version is below that uses do/next to avoid the assumption about the
number of arguments.
Also, here's a couple more samples to try:
map :add [now/date 3 now/date 4 now/date 5]
map :parse [ [1] [integer!] [a 2] [word integer!]]
map: func [
[catch]
{Map a function to a series (evaluates series elements,
unsets filtered by default).}
mapfunc [any-function! block! none!]
"Function or function body block (use VALUE as argument)."
series [series! none!] "Series to map."
/only "Inserts into result using Only refinement."
/filter filterfunc [any-function! block!]
"Single argument function or body (use VALUE as argument)."
/local result emit value pos tmp
] [
; Check arguments for none!
if any [none? :mapfunc none? series] [return none]
if block? :mapfunc [
mapfunc: func [value [any-type!]] mapfunc]
if not filter [
filterfunc: [not unset? get/any 'value]]
if block? :filterfunc [
filterfunc: func [value [any-type!]] filterfunc]
result: make type? series length? series
emit: func [value [any-type!]] compose/deep [
if filterfunc get/any 'value [
(pick [insert/only insert] found? only)
tail result get/any 'value
]
]
series: compose [mapfunc (series)]
while [greater? length? series 1] [
tmp: do/next series
emit tmp/1
remove/part next series tmp/2
]
result
]
Regards,
Brett.
[4/16] from: greggirwin:mindspring at: 22-Jul-2003 8:30
Hi Brett,
BH> Don't throw away your MAP-ish things just yet - you may not be expecting
BH> that my version evaluates the series elements, which may or may not please
BH> you :^)
Like I said, I'll have to play with it. :) Thanks for the heads-up
though!
-- Gregg
[5/16] from: andrew:martin:colenso:school at: 23-Jul-2003 9:05
Brett wrote:
> map :parse [ [1] [integer!] [a 2] [word integer!]]
Hi, Brett!
I think you meant:
map :parse [ [1] [integer!] [a 2] [word! integer!]]
As for evaluation of the series elements, if one wants the series
elements evaluated:
map :add reduce [now/date 3 now/date 4]
Using 'reduce would seem to be easier and fit better with Rebol?
And now what we really need is a 'map shootout! :)
Andrew J Martin
Attendance Officer &
Information Systems Trouble Shooter
Colenso High School
Arnold Street, Napier.
Tel: 64-6-8310180 ext 826
Fax: 64-6-8336759
http://colenso.net/scripts/Wiki.r?AJM
http://www.colenso.school.nz/
DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally
liable) for materials distributed to or acquired from user e-mail accounts. You can report
any
misuse of an e-mail account to our ICT Manager and the complaint will be investigated.
(Misuse can come in many forms, but can be viewed as any material sent/received that
indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate
language and/or other issues described in our Acceptable Use Policy.)
All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0
Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]
[6/16] from: brett:codeconscious at: 23-Jul-2003 11:48
Hi Andrew,
> I think you meant:
> map :parse [ [1] [integer!] [a 2] [word! integer!]]
Yep thanks.
> As for evaluation of the series elements, if one wants the series
> elements evaluated:
>
> map :add reduce [now/date 3 now/date 4]
>
>Using 'reduce would seem to be easier and fit better with Rebol?
My first version evaluated the series as well. In effect the function was
evaluating its arguments. I figured then, in that case, I may as well allow
expressions to be the series elements. So the input becomes a series of
expressions rather than a series of values. So I feel my second version is
more useful than my first. This may not be in the spirit of map - I don't
know.
...fit better with Rebol?
It is a good question. My second version is very
similar in capability to reduce - which implies that maybe I've gone to far
(no suprise!). I think you're right to imply that map should not overlap
with reduce. One reason I can see is that my function cannot process a
series of words (they get evaluated).
So how then can one write a map function that does not evalate the series
elements when the mapping function has multiple arguments - and preferrably
do it fairly concisely? If there isn't a way I may as well stick with what
I have.
> And now what we really need is a 'map shootout! :)
I think there might have been an informal one some time ago, but I don't
recall seeing any versions handle a mapping function with multiple
arguments. But yes, improved versions eagerly awaited here!
Regards,
Brett.
[7/16] from: andrew:martin:colenso:school at: 23-Jul-2003 14:51
Brett wrote:
> One reason I can see is that my function cannot process a series of
words (they get evaluated).
> So how then can one write a map function that does not evaluate the
series elements when the mapping function has multiple arguments - and
preferably
do it fairly concisely? If there isn't a way I may as well stick with
what I have.
Something like this perhaps?
>> a: 1
== 1
>> b: 2
== 2
>> c: 3
== 3
>> d: 4
== 4
>> map [a b c d d c b a] func [x y] [probe reduce [x y]]
[a b]
[c d]
[d c]
[b a]
== [a b c d d c b a]
>> map [a b c d d c b a] func [x y z zz] [probe reduce [x y z zz]]
[a b c d]
[d c b a]
== [a b c d d c b a]
Andrew J Martin
Attendance Officer &
Information Systems Trouble Shooter
Colenso High School
Arnold Street, Napier.
Tel: 64-6-8310180 ext 826
Fax: 64-6-8336759
http://colenso.net/scripts/Wiki.r?AJM
http://www.colenso.school.nz/
(I'm using Rebol to print out my source to avoid Rebol formatting
religious wars...)
>> source map
map: func [
{Maps or applies the function to all elements of the series.}
[catch]
Arg1 [any-function! series!]
Arg2 [any-function! series!]
/Only "Inserts the result of the function as a series."
/Full "Doesn't ignore none! values." /local
Result Results Function Series][
throw-on-error [
any [
all [
any-function? :Arg1 series? :Arg2
(Function: :Arg1 Series: :Arg2)
]
all [
any-function? :Arg2 series? :Arg1
(Function: :Arg2 Series: :Arg1)
]
throw make error! reduce [
'script 'cannot-use rejoin [
{"} mold 'Map " " mold type? :Arg1 {"}
]
rejoin [
{"} mold type? :Arg2 {"}
]
]
]
Results: make Series length? Series
do compose/deep [
foreach [(Arguments :Function)] Series [
if (
either Full [
compose [not unset? set/any 'Result Function
(Arguments :Function)]
] [
compose/deep [
all [
not unset? set/any 'Result Function
(Arguments :Function)
not none? Result
]
]
]
)
[
(either Only ['insert/only] ['insert]) tail Results
:Result
]
]
]
Results
]
]
>> source Arguments
Arguments: func [
"Returns the arguments of the function as a block."
F [any-function!] /local
Arguments][
Arguments: make block! 2
foreach Argument pick :F 1 [
if refinement? :Argument [
break
]
insert tail Arguments :Argument
]
Arguments
]
DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally
liable) for materials distributed to or acquired from user e-mail accounts. You can report
any
misuse of an e-mail account to our ICT Manager and the complaint will be investigated.
(Misuse can come in many forms, but can be viewed as any material sent/received that
indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate
language and/or other issues described in our Acceptable Use Policy.)
All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0
Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]
[8/16] from: antonr:iinet:au at: 23-Jul-2003 12:58
Looks good.
Is there a direct link to the script?
The flattening a tree example shows a good use.
I did this recently "by hand" in one of my scripts.
Now to remember which program it was...
Anton.
[9/16] from: brett:codeconscious at: 23-Jul-2003 14:15
Andrew wrote:
> Something like this perhaps?
Yes!
Did you just cook that up or was it leftovers? :^)
Given you are already filtering none and unset maybe it could use the spice
of a filter function refinement like I had. No doubt a matter of taste ;^)
I especially like the Arguments function.
Thanks,
Brett.
[10/16] from: brett:codeconscious at: 23-Jul-2003 14:23
Anton wrote:
> Is there a direct link to the script?
Nope, not yet. All the code is in the emails - just apply clean-script.r :^)
> The flattening a tree example shows a good use.
> I did this recently "by hand" in one of my scripts.
> Now to remember which program it was...
I've done a few too and was somewhat annoyed at rewriting similar bits of
code - which I continue to do.
I recently read "Why Functional Programming Matters" and liked the
resuablity demonstrated there. Not having been introduced to functional
programming concepts until REBOL - such things seem very powerful to me.
Regards,
Brett.
[11/16] from: andrew:martin:colenso:school at: 23-Jul-2003 17:11
Rebol Cheff Brett asked:
> Did you just cook that up or was it leftovers? :^)
It was created from the best parts of the last Rebol Map shootout! :)
> Given you are already filtering none and unset maybe it could use the
spice of a filter function refinement like I had. No doubt a matter of
taste ;^)
I think I'd like that delicate /refinement, Monsieur! ;)
Andrew J Martin
Attendance Officer &
Information Systems Trouble Shooter
Colenso High School
Arnold Street, Napier.
Tel: 64-6-8310180 ext 826
Fax: 64-6-8336759
http://colenso.net/scripts/Wiki.r?AJM
http://www.colenso.school.nz/
[12/16] from: brett:codeconscious at: 23-Jul-2003 22:54
For amusement, below is a function (built with inspiration from Andrew's
function) that creates a mapping function.
>> f: mapfun :add
>> f [1 2 3 4]
== [3 7]
>> g: mapfun [to word! append to string! value 1]
>> g [a b c d]
== [a1 b1 c1 d1]
mapfun: func [
[catch]
"Returns a function that maps the specified function onto a series
(unsets filtered by default)."
mapfunc [any-function! block!]
"Function or function body block (use VALUE as argument)."
/only "Inserts into result using Only refinement."
/filter filterfunc [any-function! block!] "Filter specification."
"Single argument function or body (use VALUE as argument)."
/local args
] [
if block? :mapfunc [
mapfunc: func [value [any-type!]] mapfunc]
if not filter [
filterfunc: [not unset? get/any 'value]]
if block? :filterfunc [
filterfunc: func [value [any-type!]] filterfunc]
args: head clear any [
find first :mapfunc refinement! tail first :mapfunc]
func [series [series!] /local _nfv_ _nfr_] compose/deep [
_nfr_: make type? series length? series
foreach [(args)] series [
if filterfunc set/any '_nfv_ (:mapfunc) (args) [
(pick [insert/only insert] found? only)
tail _nfr_ get/any '_nfv_
]
]
_nfr_
]
]
Regards,
Brett.
[13/16] from: andrew:martin:colenso:school at: 24-Jul-2003 10:26
Brett wrote:
> args: head clear any [
> find first :mapfunc refinement! tail first :mapfunc]
Thanks for that Brett! I had forgotten that the latest Rebol versions
return a copy of the function's argument words.
Andrew J Martin
That which does not kill my 'Map, makes it stronger! :)
Attendance Officer &
Information Systems Trouble Shooter
Colenso High School
Arnold Street, Napier.
Tel: 64-6-8310180 ext 826
Fax: 64-6-8336759
http://colenso.net/scripts/Wiki.r?AJM
http://www.colenso.school.nz/
DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally
liable) for materials distributed to or acquired from user e-mail accounts. You can report
any
misuse of an e-mail account to our ICT Manager and the complaint will be investigated.
(Misuse can come in many forms, but can be viewed as any material sent/received that
indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate
language and/or other issues described in our Acceptable Use Policy.)
All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0
Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]
[14/16] from: andrew:martin:colenso:school at: 24-Jul-2003 13:45
Brett wrote:
> args: head clear any [
> find first :mapfunc refinement! tail first :mapfunc]
<<quoted lines omitted: 8>>
> _nfr_
> ]
Just been thinking about this, and I thought, what if the 'mapfun copied
across the function's refinements and refinement arguments? For example:
MyParse: mapfun :parse
MyParse/all MyBlockOfStringsAndParseRules
MyParse MyBlockOfBlocksAndParseRules
Andrew J Martin
Attendance Officer &
Information Systems Trouble Shooter
Colenso High School
Arnold Street, Napier.
Tel: 64-6-8310180 ext 826
Fax: 64-6-8336759
http://colenso.net/scripts/Wiki.r?AJM
http://www.colenso.school.nz/
DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally
liable) for materials distributed to or acquired from user e-mail accounts. You can report
any
misuse of an e-mail account to our ICT Manager and the complaint will be investigated.
(Misuse can come in many forms, but can be viewed as any material sent/received that
indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate
language and/or other issues described in our Acceptable Use Policy.)
All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0
Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]
[15/16] from: brett:codeconscious at: 24-Jul-2003 15:37
Andrew wrote:
> Just been thinking about this, and I thought, what if the 'mapfun copied
> across the function's refinements and refinement arguments? For example:
>
> MyParse: mapfun :parse
I actually started looking at it then I caught myself before heading into
time dimished oblivion. :^)
Following your point about reduce and map, this idea made me wonder if map
can be broken down into smaller bits and whether I could look at the
functionality another way.
I've found that I can look at it in another way which I have put it in a new
thread "COLLECTing results".
Regards,
Brett.
[16/16] from: AJMartin:orcon at: 24-Jul-2003 17:42
Brett wrote:
> (pick [insert/only insert] found? only)
This is a bit quicker and shorter:
(pick [insert insert/only] not only)
That's because it uses the native 'not not the mezzanine 'found?.
Andrew J Martin
ICQ: 26227169 http://www.rebol.it/Valley/ http://Valley.150m.com/
Notes
- Quoted lines have been omitted from some messages.
View the message alone to see the lines that have been omitted