One thought I have (which I don't know is helpful or not)

I'm wondering if a "step" in a range should be a function instead?

If we ignore the syntax sugar for now and think of a range as a stream of 
values with a start, a stop and a function that determines how to get the next 
value, then this would make sense to me:

stepper = fn previous_value -> previous_value + 1 end
Range.new(1, 10, stepper)

In which case maybe a sigil is a better approach to the syntax?

I feel like this approach may also allow ranges of letters for example:

Range.new("a", "b", fn letter -> ?letter + 1 end)

Best

Adam



> On 22 Mar 2021, at 11:52, Amos King <[email protected]> wrote:
> 
> What about something closer to Haskell’s ranges? [first, second..last] is 
> their syntax and the step in inferred by the difference between first and 
> second. 1..2..n would step by one. 1..3..n is step by two. 1..2..0 would be 
> empty, etc.
> 
> Negative steps. 1..0..-10. 1..0..10 would return an empty range.
> 
> I like this syntax because it creates an interesting logical thought as I how 
> I’m counting. I think it is a friendlier syntax that doesn’t have to be 
> explained in as much detail. 1..n makes sense when I look at it. 1..-1 also 
> makes sense at a glance. 1..2..10 makes sense IMO. 1..10..2 looks surprising 
> and confusing to me. 
> 
> Amos
> 
>> On Mar 22, 2021, at 06:32, José Valim <[email protected]> wrote:
>> 
>> 
>> > 1. What about using a different syntax for separating the third parameter? 
>> 
>> Suggestions are welcome. The proposed x..y:z doesn't work though, since y/z 
>> can be taken to mean keyword or an atom. And, FWIW, I didn't take x..y..z 
>> because of F#, but rather as a natural extension of .. that at least exists 
>> elsewhere too. It is important to not confuse the cause here. :)
>> 
>> > 2. What will the step-based syntax expand to in guards? Maybe `when 
>> > is_integer(foo) and foo >= 42 and foo <= 69 and rem(foo - 42), 3)`? 
>> 
>> Correct.
>> 
>> 
>> 
>> On Mon, Mar 22, 2021 at 12:16 PM Wiebe-Marten Wijnja <[email protected] 
>> <mailto:[email protected]>> wrote:
>> As someone who has encountered quite a number of situations in which an 
>> empty range would have been useful, I am very excited by this proposal!
>> 
>> 
>> 
>> Two questions:
>> 
>> 1. What about using a different syntax for separating the third parameter? 
>> 
>> If there is any way to make it more obvious that the third parameter is the 
>> step rather than the (upper) bound, then in my opinion this might be 
>> preferable over having syntax which is e.g. "just like F#'s but with 
>> opposite meaning". The less ambiguous we can make it (for people coming from 
>> other languages, and for people in general), the better.
>> Maybe `1..10:3`?
>> 
>> 2. What will the step-based syntax expand to in guards? 
>> 
>> `when foo in 42..69` expands  to `when is_integer(foo) and foo >= 42 and foo 
>> <= 69`.
>> What should `when foo in 42..69..3` (again assuming x, y, z to be literals) 
>> expand to?
>> Maybe `when is_integer(foo) and foo >= 42 and foo <= 69 and rem(foo - 42), 
>> 3)`? 
>> Or is there a better alternative?
>> 
>> 
>> 
>> ~Marten / Qqwy
>> 
>> On 22-03-2021 11:06, José Valim wrote:
>>> Note: You can also read this proposal in a gist 
>>> <https://gist.github.com/josevalim/da8f1630e5f515dc2b05aefdc5d01af7>.
>>> 
>>> This is a proposal to address some of the limitations we have in Elixir 
>>> ranges today. They are:
>>> 
>>>   * It is not possible to have ranges with custom steps
>>>   * It is not possible to have empty ranges
>>>   * Users may accidentally forget to check the range boundaries
>>> 
>>> The first limitation is clear: today our ranges are increasing (step of 1) 
>>> or decreasing (step of -1), but we cannot set arbitrary steps as in most 
>>> other languages with range. For example, we can't have a range from 1 to 9 
>>> by 2 (i.e. 1, 3, 5, 7, 9).
>>> 
>>> The second limitation is that, due to how we currently infer the direction 
>>> of ranges, it is not possible to have empty ranges. Personally, I find this 
>>> the biggest limitation of ranges. For example, take the function 
>>> `Macro.generate_arguments(n, context)` in Elixir. This is often used by 
>>> macro implementations, such as `defdelegate`, when it has to generate a 
>>> list of `n` arguments. One might try to implement this function as follows:
>>> 
>>> ```elixir
>>> def generate_arguments(n, context) do
>>>   for i <- 1..n, do: Macro.var(:"arg#{n}", context)
>>> end
>>> ```
>>> 
>>> However, because `n` may be zero, the above won't work: for `n = 0`, it 
>>> will return a list with two elements! To workaround this issue, the current 
>>> implementation works like this:
>>> 
>>> ```elixir
>>> def generate_arguments(n, context) do
>>>   tl(for i <- 0..n, do: Macro.var(:"arg#{n}", context))
>>> end
>>> ```
>>> 
>>> In other words, we have to start the range from 0 and always discard the 
>>> first element which is unclear and wasteful.
>>> 
>>> Finally, another issue that may arise with ranges is that implementations 
>>> may forget to check the range boundaries. For example, imagine you were to 
>>> implement `range_to_list/1`:
>>> 
>>> ```elixir
>>> def range_to_list(x..y), do: range_to_list(x, y)
>>> defp range_to_list(y, y), do: [y]
>>> defp range_to_list(x, y), do: [x | range_to_list(x + 1, y)]
>>> ```
>>> 
>>> While the implementation above looks correct at first glance, it will loop 
>>> forever if a decreasing range is given.
>>> 
>>> ## Solution
>>> 
>>> My solution is to support steps in Elixir ranges by adding `..` as a 
>>> ternary operator. The syntax will be a natural extension of the current 
>>> `..` operator:
>>> 
>>> ```elixir
>>> start..stop..step
>>> ```
>>> 
>>> Where `..step` is optional. This syntax is also available in F#, except F# 
>>> uses:
>>> 
>>> ```elixir
>>> start..step..stop
>>> ```
>>> 
>>> However, I propose for step to be the last element because it mirrors an 
>>> optional argument (and optional arguments in Elixir are typically last).
>>> 
>>> The ternary operator solves the three problems above:
>>> 
>>> > It is not possible to have ranges with steps
>>> 
>>> Now you can write `1..9..2` (from 1 to 9 by 2).
>>> 
>>> > It is not possible to have empty ranges
>>> 
>>> This can be addressed by explicitly passing the step to be 1, instead of 
>>> letting Elixir infer it. The `generate_arguments` function may now be 
>>> implemented as:
>>> 
>>> ```elixir
>>> def generate_arguments(n, context) do
>>>   for i <- 1..n..1, do: Macro.var(:"arg#{n}", context)
>>> end
>>> ```
>>> 
>>> For `n = 0`, it will construct `1..0..1`, an empty range.
>>> 
>>> Note `1..0..1` is distinct from `1..0`: the latter is equal to `1..0..-1`, 
>>> a decreasing range of two elements: `1` and `0`. To avoid confusion, we 
>>> plan to deprecate inferred decreasing ranges in the future.
>>> 
>>> > Users may accidentally forget to check the range boundaries
>>> 
>>> If we introduce ranges with step and the ternary operator, we can forbid 
>>> users to write `x..y` in patterns. Doing so will emit a warning and request 
>>> them to write `x..y..z` instead, forcing them to explicitly consider the 
>>> step, even if they match on the step to be 1. In my opinion, this is the 
>>> biggest reason to add the ternary operator: to provide a convenient and 
>>> correct way for users to match on ranges with steps.
>>> 
>>> ## Implementation
>>> 
>>> The implementation happens in three steps:
>>> 
>>>   1. Add `..` as a ternary operator. `x..y..z` will have the AST of `{:.., 
>>> meta, [x, y, z]}`
>>> 
>>>   2. Add the `:step` to range structs and implement `Kernel.".."/3`
>>> 
>>>   3. Add deprecations. To follow Elixir's deprecation policy, the 
>>> deprecation warnings shall only be emitted 4 Elixir versions after ranges 
>>> with steps are added (most likely on v1.16):
>>> 
>>>       * Deprecate `x..y` as a shortcut for a decreasing range in favor of 
>>> `x..y..-1`. The reason for this deprecation is because a non-empty range is 
>>> more common than a decreasing range, so we want to optimize for that. 
>>> Furthermore, having a step with a default of 1 is clearer than having a 
>>> step that varies based on the arguments. Of course, we can only effectively 
>>> change the defaults on Elixir v2.0, which is still not scheduled or planned.
>>> 
>>>       * Deprecate `x..y` in patterns, require `x..y..z` instead. This will 
>>> become an error on Elixir v2.0.
>>> 
>>>       * Deprecate `x..y` in guards unless the arguments are literals (i.e. 
>>> `1..3` is fine, but not `1..y` or `x..1` or `x..y`). This is necessary 
>>> because `x..y` may be a decreasing range and there is no way we can warn 
>>> about said cases in guards, so we need to restrict at the syntax level. For 
>>> non-literals, you should either remove the range or use an explicit step. 
>>> On Elixir v2.0, `x..y` in guards will always mean a range with step of 1.
>>> 
>>> 
>>> -- 
>>> You received this message because you are subscribed to the Google Groups 
>>> "elixir-lang-core" group.
>>> To unsubscribe from this group and stop receiving emails from it, send an 
>>> email to [email protected] 
>>> <mailto:[email protected]>.
>>> To view this discussion on the web visit 
>>> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2BxGUW-nBj0qqRygR_-J05c05bW6mpDV9ki-HPCvfrudQ%40mail.gmail.com
>>>  
>>> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2BxGUW-nBj0qqRygR_-J05c05bW6mpDV9ki-HPCvfrudQ%40mail.gmail.com?utm_medium=email&utm_source=footer>.
>> 
>> 
>> -- 
>> You received this message because you are subscribed to the Google Groups 
>> "elixir-lang-core" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to [email protected] 
>> <mailto:[email protected]>.
>> To view this discussion on the web visit 
>> https://groups.google.com/d/msgid/elixir-lang-core/e1f904b3-3cd2-0ef1-f438-8408f5102c48%40resilia.nl
>>  
>> <https://groups.google.com/d/msgid/elixir-lang-core/e1f904b3-3cd2-0ef1-f438-8408f5102c48%40resilia.nl?utm_medium=email&utm_source=footer>.
>> 
>> -- 
>> You received this message because you are subscribed to the Google Groups 
>> "elixir-lang-core" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to [email protected] 
>> <mailto:[email protected]>.
>> To view this discussion on the web visit 
>> https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2BX2CPbHsMgM0vMOpmV%2BjvE26r%2Bw-%2BmafnQC5i-G8Qspg%40mail.gmail.com
>>  
>> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2BX2CPbHsMgM0vMOpmV%2BjvE26r%2Bw-%2BmafnQC5i-G8Qspg%40mail.gmail.com?utm_medium=email&utm_source=footer>.
> 
> 
> -- 
> You received this message because you are subscribed to the Google Groups 
> "elixir-lang-core" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to [email protected] 
> <mailto:[email protected]>.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/elixir-lang-core/7F881DB7-5E72-4DEC-AE89-9558E72E253F%40binarynoggin.com
>  
> <https://groups.google.com/d/msgid/elixir-lang-core/7F881DB7-5E72-4DEC-AE89-9558E72E253F%40binarynoggin.com?utm_medium=email&utm_source=footer>.

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/AAC916F0-3998-44FF-ACC0-B4505B3A0630%40a-corp.co.uk.

Reply via email to