> 1. x..y and x..y..z won't be allowed in patterns (you will have to
> match on %Range{})José, wouldn't this be a backward incompatible introduction? Even if we still soft deprecated and emit a warning, there could be a lot of changes to do, since this is not just a function call that needs to be updated, but a syntax one. Have you considered making a this a new data type with its own syntax? For example: 1...100...2 On Mon, 22 Mar 2021 13:38:52 +0100 José Valim <[email protected]> wrote: > Hi Amos, I considered the Haskell approach, but the issue is really > pattern matching: > > What should > > x..y..z = range > > match on? > > If we want to keep creation and matching consistenting, then it has > to be first..second..last, which means everyone now has to compute > the step. It also means checking if it is an increased range or > decreasing range is more verbose too, we always have to do: y - x > > 0, as well as the guard checks. > > Therefore, if we want to go down this route, we need to accept the > following trade-offs: > > 1. x..y and x..y..z won't be allowed in patterns (you will have to > match on %Range{}) > > 2. We need to manually compute the steps by hand in almost all range > operations > > > On Mon, Mar 22, 2021 at 12:52 PM 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]> 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 > > <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]. > > 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/6058941c.1c69fb81.20671.b223SMTPIN_ADDED_MISSING%40gmr-mx.google.com.
