Should I count up in Perl 6 with a sequence or a range?
Asked Answered
K

3

11

Perl 6 has lazy lists, but it also has unbounded Range objects. Which one should you choose for counting up by whole numbers?

And there's unbounded Range with two dots:

0 .. *

There's the Seq (sequence) with three dots:

0 ... *

A Range generates lists of consecutives thingys using their natural order. It inherits from Iterable, but also Positional so you can index a range. You can check if something is within a Range, but that's not part of the task.

A Seq can generate just about anything you like as long as it knows how to get to the next element. It inherits from Iterable, but also PositionalBindFailover which fakes the Positional stuff through a cache and list conversion. I don't think that a big deal if you're only moving from one element to the next.

I'm going back and forth on this. At the moment I'm thinking it's Range.

Karr answered 12/10, 2016 at 20:50 Comment(3)
the perl6-language mailing list or the #perl6 IRC channel on freenode might be a better place for such a question...Paulettepauley
No one will find the answer after a week. People find answers on Stackoverflow.Karr
but stackoverflow is supposed to be more about practical problems and less about the intricacies of language semantics; I doubt you'd be satisfied with the pratical answer (use Range as .. is shorter than ... :p); mind, I'm happy to answer such questions, but I'm not sure it really fits here...Paulettepauley
P
10

Semantically speaking, a Range is a static thing (a bounded set of values), a Seq is a dynamic thing (a value generator) and a lazy List a static view of a dynamic thing (an immutable cache for generated values).

Rule of thumb: Prefer static over dynamic, but simple over complex.

In addition, a Seq is an iterable thing, a List is an iterable positional thing, and a Range is an ordered iterable positional thing.

Rule of thumb: Go with the most generic or most specific depending on context.

As we're dealing with iteration only and are not interested in positional access or bounds, using a Seq (which is essentially a boxed Iterator) seems like a natural choice. However, ordered sets of consecutive integers are exactly what an integer Range represents, and personally that's what I would see as most appropriate for your particular use case.

When there is no clear choice, I tend to prefer ranges for their simplicity anyway (and try to avoid lazy lists, the heavy-weight).

Note that the language syntax also nudges you in the direction of Range, which are rather heavily Huffman-coded (two-char infix .., one-char prefix ^).

Paulettepauley answered 12/10, 2016 at 21:58 Comment(2)
@briandfoy: I added some rationale for my preferencesPaulettepauley
"iteration always happens in terms of Seq anyway" - I don't think for 1..* { } ever constructs a Seq. As I understand it, the for loop 1) sees that it got an Iterable, 2) calls .iterator on it to get an Iterator, 3) keeps calling .pull-one on that iterator until it gets an IterationEnd.Scabies
S
12

Both 0 .. * and 0 ... * are fine.

  • Iterating over them, for example with a for loop, has exactly the same effect in both cases. (Neither will leak memory by keeping around already iterated elements.)
  • Assigning them to a @ variable produces the same lazy Array.

So as long as you only want to count up numbers to infinity by a step of 1, I don't see a downside to either.

The ... sequence construction operator is more generic though, in that it can also be used to

  • count with a different step (1, 3 ... *)
  • count downwards (10 ... -Inf)
  • follow a geometric sequence (2, 4, 8 ... *)
  • follow a custom iteration formula (1, 1, *+* ... *)

so when I need to do something like that, then I'd consider using ... for any nearby and related "count up by one" as well, for consistency.

On the other hand:

  • A Range can be indexed efficiently without having to generate and cache all preceding elements, so if you want to index your counter in addition to iterating over it, it is preferable. The same goes for other list operations that deal with element positions, like reverse: Range has efficient overloads for them, whereas using them on a Seq has to iterate and cache its elements first.
  • If you want to count upwards to a variable end-point (as in 1 .. $n), it's safer to use a Range because you can be sure it'll never count downwards, no matter what $n is. (If the endpoint is less than the startpoint, as in 1 .. 0, it will behave as an empty sequence when iterated, which tends to get edge-cases right in practice.)
    Conversely, if you want to safely count downwards ensuring it will never unexpectedly count upwards, you can use reverse 1 .. $n.
  • Lastly, a Range is a more specific/high-level representation of the concept of "numbers from x to y", whereas a Seq represents the more generic concept of "a sequence of values". A Seq is, in general, driven by arbitrary generator code (see gather/take) - the ... operator is just semantic sugar for creating some common types of sequences. So it may feel more declarative to use a Range when "numbers from x to y" is the concept you want to express. But I suppose that's a purely psychological concern... :P
Scabies answered 12/10, 2016 at 21:35 Comment(0)
P
10

Semantically speaking, a Range is a static thing (a bounded set of values), a Seq is a dynamic thing (a value generator) and a lazy List a static view of a dynamic thing (an immutable cache for generated values).

Rule of thumb: Prefer static over dynamic, but simple over complex.

In addition, a Seq is an iterable thing, a List is an iterable positional thing, and a Range is an ordered iterable positional thing.

Rule of thumb: Go with the most generic or most specific depending on context.

As we're dealing with iteration only and are not interested in positional access or bounds, using a Seq (which is essentially a boxed Iterator) seems like a natural choice. However, ordered sets of consecutive integers are exactly what an integer Range represents, and personally that's what I would see as most appropriate for your particular use case.

When there is no clear choice, I tend to prefer ranges for their simplicity anyway (and try to avoid lazy lists, the heavy-weight).

Note that the language syntax also nudges you in the direction of Range, which are rather heavily Huffman-coded (two-char infix .., one-char prefix ^).

Paulettepauley answered 12/10, 2016 at 21:58 Comment(2)
@briandfoy: I added some rationale for my preferencesPaulettepauley
"iteration always happens in terms of Seq anyway" - I don't think for 1..* { } ever constructs a Seq. As I understand it, the for loop 1) sees that it got an Iterable, 2) calls .iterator on it to get an Iterator, 3) keeps calling .pull-one on that iterator until it gets an IterationEnd.Scabies
D
0

There is a difference between ".." (Range) and "..." (Seq):

$ perl6
> 1..10
1..10
> 1...10
(1 2 3 4 5 6 7 8 9 10)
> 2,4...10
(2 4 6 8 10)
> (3,6...*)[^5]
(3 6 9 12 15)

The "..." operator can intuit patterns!

https://docs.perl6.org/language/operators#index-entry-..._operators

As I understand, you can traverse a Seq only once. It's meant for streaming where you don't need to go back (e.g., a file). I would think a Range should be a fine choice.

Dorrisdorry answered 12/10, 2016 at 21:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.