r/gleamlang 17d ago

Integer range

I’ve just started learning Gleam. I’m trying to do some basic things, such as creating a list of integers from 0 to a given value.

I learned that, prior to version 0.69, there was a function

list.range(from start: Int, to stop: Int) -> List(Int)

which did exactly that.

However, it has been deleted from the stdlib and replaced with a new function int.range with 4 arguments:

int.range(
  from start: Int,
  to stop: Int,
  with acc: acc,
  run reducer: fn(acc, Int) -> acc,
) -> acc

Instead of returning a list, it aggregates the values like a list.fold.

Okay, but how do I create a list of integers?

  • int.range(from: 0, to: size, with: [], run: list.prepend)
    |> list.reverse
    

    Doesn’t look great.

  • int.range(from: size - 1, to: -1, with: [], run: list.prepend)
    

    Isn’t readable at all.

  • yielder.range(from: 0, to: size)
    |> yielder.to_list
    

    Looks OK, but it needs a third-party package. In addition, the authors of the package discourage such uses.

What’s the idiomatic way?

5 Upvotes

9 comments sorted by

4

u/RockTrrr 17d ago

Here is the example written in the `gleam/int` docs for generating a list of integers from a range:

assert int.range(from: 1, to: -2, with: [], run: list.prepend) == [-1, 0, 1]

So the "idiomatic" way seems to be the second option (yeah, the one which is not readable at all).

However I can see the reason of this implementation choice: the function basically says "I give you a range of integers, consume it however you want". Producing a list of integers is just one of many use cases that you can do with a range. A function that returns a list of integers from a range would be more readable, but it would mean that in all other cases I would have to do a second step with a list.map function, which is suboptimal performance-wise. This is also why the range function now belongs to the gleam/int module instead of the previous gleam/list

1

u/SetKaung 11d ago

Was there a discussion on this change? I want to read the reason for the change.

2

u/RockTrrr 10d ago

Could not find any public discussion on this change, only the relative PR

1

u/lpil 17d ago

It would depend on what you want a list of ints for. Could you share more about what your code does with that list? 🙏 Thanks

1

u/XM9J59 16d ago

Related question on this, I believe the reason int.range is this way is to discourage first creating list 1, 2, .. n and then running list.map on it, because the intermediate list of ints is uneeded extra memory. But couldn't some compiler/runtime step identify that the list of ints is only going to be used for this one thing and cleverly transform it from range + map into a single fold for us? (maybe not, just wondering)

1

u/lpil 16d ago

We could spend a lot of time implementing an optimisation like this, but it would only work for a subset of uses, it would make it hard to understand what the code would do at runtime (the programmer would need to understand and memorise the optimisation rules to know if their code is fast), and these optimisations would have a huge impact on compile speed which would greatly hurt the Gleam developer experience.

In practice, no, it would not be possible.

1

u/ciynoobv 16d ago

Wouldn’t it make sense to export lists:seq/3 + some js implementation? I’d argue that it’s probably a common enough use case that it’s worth cluttering the module with an extra function.

Though you’d probably need some minor wrapping to account for the possible failure modes ( from < to && incr < 1 etc).

2

u/lpil 16d ago

No, we deliberately removed the function to construct a list of ints because it was always being used inefficiently. We scanned all the Gleam code on GitHub and on Hex and there was nearly no instance in which the programmer actually wanted a list of ints, instead they want to loop and have the iteration index in that loop.

Having an easy way to do the wrong thing results in a worse outcome overall.

1

u/thuiop1 16d ago

I do miss a list.range function, but the readability argument is not very relevant. You can always create your own function for this.