I have a feeling this would be better addressed with two syntax constructs.
Isn't the issue of empty ranges really an issue of the range being
right-closed without a way to say it should be right-open? So x..y.. could
mean from x to y, excluding y.
I think the step part would be better addressed with something like x..y|z
(or maybe \\ instead of |).

pon., 22 mar 2021 o 13:49 José Valim <[email protected]> napisał(a):

> FWIW, the reason why Haskell doesn't have those trade-offs is because,
> afaik, the syntax is really a shortcut for a lazy list. This doesn't work
> for us because Range is really a specific data type that we want to
> introspect. So we need to represent the data in a way that is good for both.
>
> > I'm wondering if a "step" in a range should be a function instead?
>
> A function cannot be invoked in guards. So that rules it out. It has to be
> an integer.
>
>
> On Mon, Mar 22, 2021 at 1:38 PM 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/CAGnRm4JL-Ge-c5LF%3DH5pCiGeDwug1Nt6c-fMzuxGizaeh_%2BECA%40mail.gmail.com
> <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JL-Ge-c5LF%3DH5pCiGeDwug1Nt6c-fMzuxGizaeh_%2BECA%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/CACzMe7asjL71%2BT32rpmjH%2B3pPnHQzaz9x2NSuEJdGvwcozCtqA%40mail.gmail.com.

Reply via email to