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

Reply via email to