The <numbers from ___ to ___> block no longer returns empty list when first number is greater than second

According to the help of the block, the following block should result in an empty list: <numbers from 5 to 1>. This was true for me up until very recently, where now it is resulting in the list [5, 4, 3, 2, 1].

The original way was very useful. For example, if you wanted to say all but the last two elements of a list, you could do:

for each INDEX in <numbers from 1 to (<length of MY_LIST> - 2)>:
  say <item INDEX of MY_LIST>

However, now with the block also returning descending lists, the above code does not work when MY_LIST is short. For example, if MY_LIST has two items, then the above script will say the second item in the list, and then the first item in the list, when in fact it should say nothing.

Yeah, this was a recent change. We figured that it would be helpful for some applications and we hoped it wouldn't break too many programs. So it's interesting getting a complaint two days later. We might have to rethink it. Anyone else want to weigh in on this?

Welcome to the forum, by the way!

Thank you for the welcome.

What was the reason for the recent change? Maybe renaming the original block would also resolve the reason (e.g. <ascending ___ to ___>)?

Here's my arguments for a block like the original <numbers from ___ to ___>:

  • How would you write a function that returns the odd indexed elements of a list (e.g. [1, 2, 4, 8, 16] --> [1, 4, 16])? How does it handle an empty list? With the new block, I can't think of a way that does not include explicitly checking if the length of the list is 0 (e.g. first doing "if length of MY_LIST = 0: report empty list").
  • Having a block like the original means you don't have to explicitly check if start <= end, which reduces code complexity and thus makes code less error prone.
  • The reduced code complexity is probably why Python's range function and the new Java8 IntStream.range function also return empty iterators when start > end.
  • The original block acted like how for (int i = startInclusive; i <= endInclusive ; i++) loop would work.

Interesting discussion! The idea to also support counting down was mainly to be consistent with what we're doing in the FOR block already, and that returning an empty list instead of a decrementing one would probably not be what the users are expecting. So, yours is an interesting use case that exploits that quirk we had before! (I was actually somewhat surprised that we didn't count down already ... and I wrote that code myself, haha!).

So, to generalize the issue of returning all but the last n items of a list you could write your own block such as this one:

which you might further condense using the reporter-if block to:

To write a function that returns the even elements of a list you could write:

Does that make sense to you?

I'm working with high school students in an introduction to computer science class (TEALS). The "all but last" question was a simplified version of a question in the second lab students do on lists (2.3.4 Not first 2 or last 2 Given that it took me quite a bit of time to understand your recursive solution, I doubt students will get it. Recursion isn't part of the TEALS curriculum, though I am trying to add it to our class this year ( For comparison, here's what I had originally:

Here is another assignment: How would you do question 1.4 of that, which is to write a custom SNAP predicate block called "increasing?" that takes a list of numbers as an argument and reports true if each value in the list is greater than or equal to the one before it.

Here's how I had it originally.


It crazily reports true for an empty list even though <numbers from 1 to -1> --> [1, 0, -1]. But there's a bug now for <increasing? [5]> which now reports false. How do I fix this? As far as I can tell, I need to hand check that my list has at least length 2.

I have no idea how hard this is to implement, but what about:

  • Revert <numbers from ___ to ___> back to original way, and rename it to <ascending ___ to ___>
  • Add a <reverse ___> block that returns a new list containing the given list's elements in reverse order.

Then if you wanted a descending list of numbers you would call <reverse <ascending ___ to ___>>.

Either way, thank you for all the work on Snap, and making the our introduction computer science class possible!

thanks for that thoughtful answer. I fully understand that recursion might be pushing the envelope of your class. So, one thing to remember about Snap! lists is that you don't get an out-of-bounds-error when you access a list by an index that doesn't exist. Instead you get "nothing" or zero. Provided that you only want to check for increasing positive numbers, you might want to try something like this:

or do you think that rings are also beyond the scope of your class? I'm really unsure here, because I'm not a teacher myself. Maybe @bh can weigh in on this....

I'm not set to insisting that our numbers block also has to support counting down. And your pointing out that we also have reverse for lists in libraries is very valid. So let's think about this some more!

Thanks for your quick responses jens.

I had no idea about the additional idx and data inputs until now. I tried it myself. I'm not sure I understand why the numbers need to be positive. When I try for list = [-5], it reports false. Is that because -5 is compared to "item -1 of [-5]" which is nothing, and so that matches?

do you think that rings are also beyond the scope of your class?

I'd also like some input from others. To me this is beyond the scope of our intro cs class.

So, @jens, when you implemented NUMBERS as a primitive we had a brief conversation (in my home office, I'm pretty sure) about this in which you asked what the Tools version did, and I said it returned an empty list in that case.

To tell you the truth, I now think that my Tools implementation of FOR was a mistake, and it shouldn't work backward either. We could instead give a really specific error message with a suggested rewriting. I wrote FOR the way I did because I wanted to show off assigning a ringed predicate expression to a variable so the loop code could be IF CALL (ENDTEST)) ... but with FOR primitive, that reason no longer applies. :~)

But, @milliones, your examples all look strange to me because you're applying FOR EACH to the NUMBERS list rather than to the list you're really interested in. It helps that you change the name of the upvar from ITEM to INDEX, but it'd be much better not to use FOR EACH at all, and instead we find a way to do it functionally.

So, @jens, how about if the ring in MAP, KEEP, and FOR EACH (but not COMBINE) automatically generates names ITEM, INDEX, and LIST (or DATA, which I have mostly been using since a student complained that an orange LIST variable block looks too much like a red empty LIST block) for the first three formal parameters? That would make this super-undiscoverable feature a little easier to grok. (If the user drops a ring that already has formal parameters into that slot, though, use the user's names.)

That would mean we have to promote this obscure bit of syntax into a part of the curricula. (Remember, the TEALS intro course is meant to be prior to CSP, so TEALS and BJC would both use this.) I'm not sure I like that idea, but whether we add this to the curriculum or not, I still vote for using the meaningful names as the formal parameters if any.

(At this moment I'm toying with the idea that we include ITEM as a visible formal parameter in the HOFs, and users could click right for more choices or click left for empty slot filling. But I don't really like it. I'd almost rather have a per-user setting to start with 0 or 3 formal params.)

By the way, the Official Correct Way to implement INCREASING? is to load the list operations library (the second one in the Libraries list) and use a two-list MAP:
This takes some wrapping your head around, but it has the great virtue that it doesn't use list indices at all, which is the right way to think about lists.

(@jens: If the primitive COMBINE knew about the base cases for the Only Six Blocks You Ever Use With Combine, then the IF special case here wouldn't be necessary. MAP would report an empty list, and COMBINE would report True, which is the base case for AND.)

But, @milliones, back to reality, I'm tempted to say that if you're looking at indices in FOR EACH, you might as well just use a FOR, and not have to use NUMBERS at all. Our big push is on using functional programming where it's appropriate, and both ODD-ITEMS and INCREASING? are functions, so the goal is not to have to use commands (such as FOR and FOR EACH, and the things you'd do inside their input scripts) altogether. That's why the recursive versions, and my two-list MAP version, and Jens's ITEM/INDEX/LIST inputs versions, are preferable, imho. (And, yes, this puts us at odds with the CSP Framework, but in BJC we do the render-unto-Caesar thing by saying "by the way, on the exam they'll want you to recognize this ugly way to do it.")

This is probably more advice than either of you wanted!

But, @milliones, back to reality, I'm tempted to say that if you're looking at indices in FOR EACH, you might as well just use a FOR, and not have to use NUMBERS at all.

Is this assuming the FOR no longer does descending? If so, then I agree 100%. The only reason I was doing the FOR EACH with NUMBERS was to avoid descending.

Oh. Jens is pretty set on allowing descending in both cases.

In Logo, we made liberal use of special reserved global variables controlling various features of the language: CASEIGNOREDP (for string comparisons; case is always ignored in names of procedures and variables), ERRACT (instruction list to be run if an error isn't otherwise caught), REDEFP (can primitives be redefined), ... 16 of them in all. The virtue of variables rather than special settings like our FLAT LINE ENDS in a dynamically scoped language is that a procedure can locally rebind one of them, and the setting applies also to its subprocedures, and is automatically reset when the procedure exits.

But I'm guessing Jens wouldn't go for it.

Edit: Of course you can write your own FOR!

Example TEALS Problems and Proposed Solutions:

  • Say all but the first two and last two items in a list. The proposed solution used recursion, which is not part of the TEALS standard curriculum.
  • Say odd-indexed items. The proposed solution used the undocumented idx argument to the "keep items" block.
  • Determine if a list is increasing. The "Official Correct Way" uses a block that I can't find (where is the "# map ____ over" block that can map over two lists?).

Another general proposed solution is to write own FOR. How do you create a custom C-block? My best interpretation of this is to create a custom "ascending ___ to ___" block that acts like "numbers from ___ to ___" but returns an empty list if the first arg is greater than the second.

To make a custom C-shaped block, give it an input for the C-shaped slot and declare that input to be of type "Command (c-shape)":

But we should have a better solution.

@jens, can we schedule a phone meeting about this TEALS issue?

So, after a long discussion, we think that we shouldn't keep changing, and we're happy with how it is, and it's relatively easy to write your own version to give your students. The zero-trip FOR is what we have on our web site as the paradigmatic example of a DIY control structure:


And it just this second occurred to me that you could make a FOR-end-test-agnostic NUMBERS:

Isn't that cute? If FOR is ascending-only, so will NUMBERS be; if FOR allows descending, so does NUMBERS. :~P

Can't we just do this:

There are two problems with that approach. First, channeling @Jens, every runtime test slows down all projects, not just the ones that care about descending order. Second, there are a lot of things for which option flags would be helpful. Top of the list: case independent comparison and case independent symbols (names of variables, blocks, etc.). But also things like degrees vs. radians, CCW-from-East vs. CW-from-West, etc.

So our general policy is just to pick one, and make sure that it's reasonably possible for users to implement the other. And to consider new option flags where users can't implement the other. (For example, case-independent symbols, one of those things about which I feel that the entire world is wrong, except for people my age who remember when computers had only capital letters.)

The other situation in which you'll see an option flag is for a brand-new feature that Jens is still testing. If you shift-click the gear menu, you'll see bazillions of those flags, no longer official.

Please make sure to update the "help..." for the "numbers from ___ to ___" block, since it currently says the the "range must be ascending."

Here is the XML to import for both an "ascend index = ___ to ___" and "descend index = ___ to ___" custom blocks that I plan to use for TEALS assignments. They are copied from your custom "for" block above, just with some renaming to avoid confusion with the existing "for" block. So thank you for that script pic, and thank you for the thoughtful discussion @bh and @jens.

<blocks app="Snap! 5.1," version="1"><block-definition s="ascend %&apos;index&apos; = %&apos;start&apos; to %&apos;end&apos; %&apos;action&apos;" type="command" category="lists"><header></header><code></code><translations></translations><inputs><input type="%upvar"></input><input type="%n">1</input><input type="%n">10</input><input type="%cs"></input></inputs><script><block s="doSetVar"><l>index</l><block var="start"/></block><block s="doUntil"><block s="reportGreaterThan"><block var="index"/><block var="end"/></block><script><block s="doRun"><block var="action"/><list></list></block><block s="doChangeVar"><l>index</l><l>1</l></block></script><comment w="215" collapsed="false">If start &gt; end, then this block does nothing. This is useful for handling the case when you want to loop through the indexes of a list with &quot;ascend index = 1 to length of my_list&quot;. This is because if the list is empty, then start &gt; end = 0. So this will correctly do nothing when my_list is in fact empty.</comment></block></script></block-definition><block-definition s="descend %&apos;index&apos; = %&apos;start&apos; to %&apos;end&apos; %&apos;action&apos;" type="command" category="lists"><header></header><code></code><translations></translations><inputs><input type="%upvar"></input><input type="%n">10</input><input type="%n">1</input><input type="%cs"></input></inputs><script><block s="doSetVar"><l>index</l><block var="start"/></block><block s="doUntil"><block s="reportLessThan"><block var="index"/><block var="end"/><comment w="217" collapsed="false">If start &lt; end, then this block does nothing. This is useful for handling the case when you want to loop BACKWARDS through the indexes of a list with &quot;descend index = length of my_list to 1&quot;.</comment></block><script><block s="doRun"><block var="action"/><list></list></block><block s="doChangeVar"><l>index</l><l>-1</l></block></script></block></script></block-definition></blocks>

Python's range!

Just this weekend I had a bug in a program that turned out to be because I was expecting the old behavior of NUMBERS. Maybe we should have a NUMBERS FROM __ UPTO __ block.

After this first year of teaching lists to students, I think a "for location and item in my_list" block would be better. In Python terms, this would be like "for location, item in enumerate(my_list)".

The ascend/descend blocks I originally proposed were problematic. Students initially struggled with remembering "item index of my_list" block, and then later struggled with how and when to use it. I believe making the custom block traverse my_list more directly could make it easier for students. Also, the descend block was only used to reverse a list. For next year, I'd just make reversing a list a bonus question.

No need for a Snap change, in my mind. I just thought I'd update this thread with recent teaching experience, since there was some recent activity on it.