> 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]> 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]. > 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]. > 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]. 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.
