gop icon indicating copy to clipboard operation
gop copied to clipboard

Support range function in Python

Open QiJune opened this issue 5 years ago • 11 comments

In Python, the range function returns a sequence of numbers, starting from 0 by default, and increments by 1 (by default), and stops before a specified number.

For example:

>>> x = range(10)
>>> print(x)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>

In machine learning, we often training a model with a number of epochs. If we want to train a model with 10 epochs, we write codes like this:

for epoch in range(10):
    # do training
    # do evaluation at the end of an epoch
    print("epoch %d: evaluation metrics", epoch)

In Go, we have to write codes like this:

for epoch := 0; epoch < 10; epoch++ {
}

for epoch in range(10) is shorter than for epoch := 0; epoch < 10; epoch++.

I am working on gotorch project, and I try to write a deep learning model example with different frontend languages https://github.com/wangkuiyi/gotorch/issues/23. Here is the training loop part.

Could we do something in Go+? Thank you!

QiJune avatar Jul 30 '20 01:07 QiJune

my proposal is as follows

[[start]...stop,[step]]

ie. println([1...6,2])

prints new array [1,3,5]

println([...5])

prints new array [0,1,2,3,4]

println([...5,2])

prints new array [0,2,4]

JessonChan avatar Jul 30 '20 01:07 JessonChan

There are two ways to reach the goal.

  1. Treat range as a builtin function.
func range(n int) []int {
  ...
}
  1. Treat range as an iterator that looks like a lazy initialized slice:
for x <- range(1e8) { // allow N be a huge number
  ...
}

xushiwei avatar Jul 31 '20 17:07 xushiwei

range should have three input paramters : start number(optional,default 0,included),step number(optional,default 1) and stop number(required,excluded)

JessonChan avatar Aug 01 '20 09:08 JessonChan

@xushiwei Good suggestions.

Actually, there are two built-in functions in Python2, range and xrange. range returns a sequence of numbers, while xrange returns an iterable object.

In Python3, there is no xrange, and range returns an iterable object.

So, I prefer the second way. It's consistent with Python3.

As @JessonChan commented, there are three input parameters of range.

// case 1
for x <- range(0, 1, 10) {
}

// case 2
for x <- range(10) {
}

// case 3
for x <- range(1, 2, 10) {
}

The case 2 is equal to case 1. However, Go does not support optional parameters.

Any suggestions from Go+? Thank you!

QiJune avatar Aug 03 '20 00:08 QiJune

However, Go does not support optional parameters.

Any suggestions from Go+? Thank you!

This is not a problem. If range is a function. it can be:

func range(start int, options ...int) []int {
   var step, stop int
   switch len(options) {
   case 0:
        start, step, stop = 0, 1, start
   case 1:
        step, stop = 1, options[0]
   case 2:
        step, stop = options[0], options[1]
   default:
        painc("...")
   }
   ...
}

xushiwei avatar Aug 03 '20 01:08 xushiwei

I would like to propose the rust/haskell syntax [1..10] (note the two dots, this is convenient because it is distinct from the three dots used for variadic functions). Haskell allows you to do steps as well: [5,10..100]. I feel like this is much more elegant than a builtin function, on top of being more concise. Also, range is already a keyword in go, and it's the one used for iterating over a list. so your code would actually look like for i := range range(10), which is not very clear at all.

lolbinarycat avatar Aug 05 '20 18:08 lolbinarycat

I would like to propose the rust/haskell syntax [1..10] (note the two dots, this is convenient because it is distinct from the three dots used for variadic functions). Haskell allows you to do steps as well: [5,10..100]. I feel like this is much more elegant than a builtin function, on top of being more concise. Also, range is already a keyword in go, and it's the one used for iterating over a list. so your code would actually look like for i := range range(10), which is not very clear at all.

It looks cool. And I think [5,10..100] is ambiguous to [5,10,100] until parsing the .. token. I think maybe [10..100:5] is better.

  • range(N): [..N] or [0..N]
  • range(M, N): [M..N]
  • range(M, step, N): [M..N:step]

xushiwei avatar Aug 05 '20 21:08 xushiwei

I really don't like having the step on the end. Haskell doesn't actually have you specify a step, I has you specify a list that you want continued. This means [7, 9..20] resolves to [7,9,11,13,15,17,19]. I don't get your statement on ambiguity, every statement is ambiguous to an infinite number of statements until the last token is parsed, why would this be any different? I will admit however that the [10..100:5] syntax is my second favorite, although I would much prefer mine.

lolbinarycat avatar Aug 05 '20 23:08 lolbinarycat

[...] is not only for variadic functions but also specifying a length equal to the number of elements in the literal of an array (array := [...]string{"a", "b", "c"}). So I prefer to use ... not .. as the notation. In List-comprehension, we use , to separate body and condition , so , is better than : in this range funciton.

JessonChan avatar Aug 06 '20 13:08 JessonChan

[...] is not only for variadic functions but also specifying a length equal to the number of elements in the literal of an array (array := [...]string{"a", "b", "c"}). So I prefer to use ... not .. as the notation. In List-comprehension, we use , to separate body and condition , so , is better than : in this range funciton.

To me, both of these facts suggest the opposite, as both of those operators already have uses, and shouldn't have even more uses added on. Related to this is the problem go is having with () vs [] for generics. The reason people don't want () is because it makes things confusing to read, as you end up with things like Foo(int,string)(i,str).

I also think that borrowing a notation from another language is a better choice for superficial things like this, otherwise people will have to remember extra syntax for no reason.

In every example you listed, It doesn't do what I expect. I especially don't like the stop argument being exclusive. [...5] resolving to [0,1,2,3,4] just seems wrong. To me, .. (or ...) represents elements being omitted from a list, so [1...6,2] represents a list that starts with 1 and ends with 6 followed by 2, which doesn't make sense. This is why I like Haskell's syntax of [1, 3..6]. It's also why I don't hate [1..6:2] as the : makes it clear that the :2 is it's own thing, not an element in the list.

There's also the basic argument that ..is less typing than ..., which is generally a good thing.

lolbinarycat avatar Aug 07 '20 17:08 lolbinarycat

They ended up going with https://github.com/goplus/gop/blob/main/doc/docs.md#range-for

for i <- :5 {
    println i
    // 0
    // 1
    // 2
    // 3
    // 4
}
for i <- 1:5 {
    println i
    // 1
    // 2
    // 3
    // 4
}
for i <- 1:5:2 {
    println i
    // 1
    // 3
}

elimisteve avatar Nov 13 '23 23:11 elimisteve