+1 for first..second..last

Jean

Le lundi 22 mars 2021 à 06:52 -0500, Amos King a écrit :
> 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/a51da42ea1004eab2b53245aeefb19c4b39827d6.camel%40gmail.com.

Reply via email to