REBOL 3.0

Comments on: TAKE Action

Carl Sassenrath, CTO
REBOL Technologies
6-Apr-2007 17:32 GMT

Article #0082
Main page || Index || Prior Article [0081] || Next Article [0083] || 13 Comments || Send feedback

A lot of language design is concern over little things - many little things.

There is a functional asymmetry in REBOL's series access model. It pops up when you write objects like I/O ports (which I tend to be doing a lot these days.) A voice has been telling me to fix this for a long time. (Is 8 years a long time?)

Those of you who use queues or stacks may already know what I'm talking about. Implement a queue (FIFO):

queue: make block! 20

; To enqueue (add new item):
append queue item

; To dequeue (get top item):
item: first queue
remove queue

(Note that in R3, first on empty queue is not an error, so the above works.)

The issue: that last section cannot be written as a single sentence. The function stream is interrupted, which becomes obvious in this case:

dequeue: func [queue /local item][
    item: first queue
    remove item
    item
]

So, enqueuing is a singular series action, but dequeue cannot be.

This violates the "simple as possible" design rule of REBOL... because there is indeed a simple alternative.

The solution is:

; To dequeue (get top item):
item: take queue

Here, take is equivalent to a first and remove on the series. The dequeue function written above becomes trivial, so you no longer need it.

What if you want to take more than just one?

items: take/part queue 3

and now items is a series itself.

And finally, what about stacks?

We know that implementing a stack with top-at-tail gets us the best performance. So, we would want:

item: take/last queue

which roughly does a:

item: take back tail queue

We can also allow:

items: take/last/part queue -3

The reason for the negative number comes from this property:

items: take/part at queue 5 -3

So, take works anywhere within the series.

13 Comments

Comments:

Edoc
6-Apr-2007 14:19:12
I like the functionality. Might common stack functions names (e.g., pop, push, top, etc.) be more appropriate here?
Gregg Irwin
6-Apr-2007 19:18:55
Edoc, I don't want push, pop, etc. as standard; you can alias them easily enough if you want. They're just more words to remember, and since a series is just a series, they would have to always operate the same; i.e. you couldn't use them for both stacks and queues.

I used to look for very specific, unique, words for methodds in each class I wrote, so stacks and queues had those specific names. After I read Bertand Meyer's 'Reusable Software', where he talks about the advantage of a Linnaean naming convention, and after finding REBOL, which uses it so well, I greatly prefer it for general use.

Gregg Irwin
6-Apr-2007 19:24:21
I don't get the need for a negative range val with /last though. I can see it working with AT, but if /last means "from the tail", then the take can only go one direction, right?

I know negative values are often used to indicate the reverse of a positive arg, but it doesn't make things clearer most of the time; it's just something to learn to watch out for.

Edoc
6-Apr-2007 19:55:14
Hi Greg-- I mention it because pop and top are already quite familiar to most programmers as list/set/stack functions and the meaning might be more straight-forward than 'take. The first time someone encounters 'take, they will definitely be headed for the REBOL function dictionary. It's no big deal to me, but 'take doesn't suggest to me that it's a destructive operation and not a copy. Just my $.02, no gripes.
Brian Hawley
7-Apr-2007 10:45:47
Edoc, here you go:
push: func [s x] [append/only :s :x]
pop: func [s] [take/last :s -1]
top: :last
Happy? :)
Brian Hawley
7-Apr-2007 11:21:37
Greg, I'm not sure I agree with you on the negative lengths. It is too valuable to be able to remove values from before the position when you are in the middle of the series. If you wanted to get rid of negative length arguments you would need to add a /BACK refinement to get the same effect, and then just say that /LAST implies /BACK. I'm not sure that I would give up the potential benefit of arithmetic on the length argument...

I'm curious how TAKE would behave in certain cases.

  • Can you return the value wrapped in a series if you TAKE/PART s 1, just like you do when you TAKE/PART s 2, for consistency? Can TAKE/PART s 0 return an empty series of the type of s, also for consistency? That would make arithmetic on the length more useful.
  • It would be nice if TAKE would return NONE if there was nothing to take, like the new behavior of FIRST.
  • What does TAKE/PART return when there aren't enough values? I would suggest a series of the length that it can get, of the values that are there. If there are no values return an empty series as above. With the changes to FIRST and such it would be the same as NONE padding, but more useful since you would get the available length too.
Overall, TAKE sounds like a great addition to the toolkit.
Edoc
7-Apr-2007 12:22:02
I'm not grumpy-- the name 'take got stuck in my craw, that's all. Maybe I'm alone on this one. Seems like there should be plenty of other candidates which describe the action more naturally. E.g., 'clip, 'cue, 'snip, etc.

Ok, I've had my say. :^)

Maxim Olivier-Adlhoch
8-Apr-2007 15:29:03
Edoc: if you look up the meaning of 'TAKE, its quite exact in this useage, take means remove from someone else which is exactly what carl decribed.

also, as noted, take can retrieve from the middle too, so the other words would become ambiguous.

Edoc
8-Apr-2007 18:31:40
No problem, I'm over it.
Gregg Irwin
9-Apr-2007 13:22:37
I think TAKE was the word that came out on top in the previous, long, discussion on this function. CUT and PULL also came up.

"I'm not sure I agree with you on the negative lengths. It is too valuable to be able to remove values from before the position when you are in the middle of the series."

I agree 100% Brian, my only issue is with requiring them when used with /LAST.

I think the behavior of /PART should match COPY/PART, while a plain TAKE is like PICK. Something like this maybe (UNTESTED! OK, I ran a couple quick tests):

    take: func [
        "Remove and return items from a series."
        series [series! none!]
        /last "Take from the tail, not the head"
        /part "The number of items to pull; one is the default"
            range [number!]
        /local result
    ] [
        if none? series [return none]
        range: any [range 1]
        either last [
            ; force negative range if taking from tail.
            series: skip tail series negate abs range
            result: either part [copy series] [system/words/last series]
            clear series
        ][
            either part [
                result: copy/part series range
                remove/part series range
            ][
                result: first series
                remove series
            ]
        ]
        result
    ]
Edoc
20-Apr-2007 12:07:33
Maybe a good name or alias for 'take/last would be 'curtail

curtail: "to cut short; cut off a part of; abridge; reduce; diminish."

Gregg Irwin
24-Apr-2007 11:19:47
Curtail is not a good word for this func IMO. For one thing, it doesn't mean the right thing if you're taking an item from anywhere other than the tail of the series.
Edoc
24-Apr-2007 14:04:15
Reading closely, I think we both agree.

Post a Comment:

You can post a comment here. Keep it on-topic.

Name:

Blog id:

R3-0082


Comment:


 Note: HTML tags allowed for: b i u li ol ul font span div a p br pre tt blockquote
 
 

This is a technical blog related to the above topic. We reserve the right to remove comments that are off-topic, irrelevant links, advertisements, spams, personal attacks, politics, religion, etc.

REBOL 3.0
Updated 22-Jul-2017 - Edit - Copyright REBOL Technologies - REBOL.net