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 <jose.va...@dashbit.co> 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 <w...@resilia.nl> 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.
>>> 
>>> 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 elixir-lang-core+unsubscr...@googlegroups.com.
>>> 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.
>> 
>> -- 
>> 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 elixir-lang-core+unsubscr...@googlegroups.com.
>> To view this discussion on the web visit 
>> https://groups.google.com/d/msgid/elixir-lang-core/e1f904b3-3cd2-0ef1-f438-8408f5102c48%40resilia.nl.
> 
> -- 
> 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 elixir-lang-core+unsubscr...@googlegroups.com.
> 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.

-- 
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 elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/7F881DB7-5E72-4DEC-AE89-9558E72E253F%40binarynoggin.com.

Reply via email to