Can `TAKE []` Just Return Null?

Here is the behavior of TAKE in Rebol2:

rebol2>> block: compose [1020 (none)]
== [1020 none]

rebol2>> take block
== 1020

rebol2>> take block
== none

 rebol2>> take block
 == none

 rebol2>> take block
 == none

One thing I was bothered about was the inability to notice a difference between taking from an empty block and taking from a block containing a NONE!. Ren-C can resolve that with NULL as a unique response to taking from a block with no elements in it.

But there's another concern which BrianH expressed, about a routine that you expected to have a side effect that did not. If you write a line simply like take block and it doesn't take anything away, should it be silent about that fact?

Early Ren-C introduced TAKE* as distinct from TAKE...

Experiments at being prescriptive worried that getting back a NULL might not be enough to warn you about a misunderstanding. So we wound up with this:

ren-c>> take []
** Error: TAKE-ing from an empty block, use TAKE*

ren-c>> take* []
; null

To make things simple, TAKE was just a chained function derived from the lower-level TAKE*. This seemed "safer"...at the time, and addressed BrianH's (pre-NULL) concern...as well as my general overarching desire to use things that are "more checked".

But this may be the wrong place to get paranoid about the user's intent, and we should just be happy that the NULL offers a unique result that means nothing was taken.

When I wrote about "Make Your Own Safety" I asked what the odds were that we could guess the top pet-peeve that every programmer would have.

How many times have you been bitten by TAKE on an empty block you didn't know was empty? When you did, how likely was it that you blamed yourself vs. the tool?

Not only that, but what if you could get the "hardened" versions of TAKE and TAKE* in the files where it was bothering you as easily as:

 take*: :lib/take
 take: (=> non null lib/take)

This seems the better angle, and avoids making it feel like there's any kind of bias toward the "tics" that bother @rgchris, which the * may represent. Give people the power to bend fast to address the specific concerns that are bothering them with the bent and personalized/customized code they are writing to solve the problem they have right now.

I don't think I have been bitten by that. For my personal preference, having TAKE return NULL from an empty block seems better than throwing an error-- the latter behavior implies that the block! cannot be-empty, which, I don't know, seems un-block-like. Using TAKE then makes a block! less block-like. I can think of all kinds of interesting variants of block! structures (constrained sizes, LIFO/FIFO) which could be convenient and safer for some TAKE scenarios.

I think this is my balance priority-wise these days. Returning NULL is enough.

When you think about it, append [a b c] [] doesn't necessarily "append anything". Is it important to tell people who say append [a b c] something that nothing actually happened if something was empty? Increasingly we've said no, that this doesn't necessarily represent a misunderstanding on the part of the programmer.

So the deed is done.

There's some ramifications on variadics, but we should be thinking if TAKE was ever the right function for variadics in the first place.

(Note: I will point out--as I often do--that some of these experiments were used as early exercises of things like CHAIN. They are thus not wasted experience...just have to focus on getting the code tighter and smarter...)

1 Like