gleam-lang / stdlib

🎁 Gleam's standard library
https://hexdocs.pm/gleam_stdlib/
Apache License 2.0
485 stars 171 forks source link

Negative indexes #42

Closed eterps closed 4 years ago

eterps commented 4 years ago

In the (TODO) doc comments of string.slice ( https://github.com/gleam-lang/stdlib/blob/master/src/gleam/string.gleam#L135 ), you see this example:

> slice("snakes on a plane!", from: -6, to: -1)
"plane"

Which surprises me because as a user of the slice function I would expect the function to be able to slice to the end of a string without having to know the length of that string beforehand.

So personally I expect to be able to do this:

> slice("freeby", from: -2, to: -1)
"by"

This is also how negative indexes work in Ruby for example (I'll have to check other languages though):

> "snakes on a plane!"[-6..-1]
=> "plane!"

Thoughts?

eterps commented 4 years ago

In Elixir enums suport negative indexes: https://hexdocs.pm/elixir/Enum.html#at/3

A negative index can be passed, which means the enumerable is enumerated once and the index is counted from the end (for example, -1 finds the last element).

eterps commented 4 years ago

With Elixir strings you can do:

iex(1)> "snakes on a plane!" |> String.slice(-6..-1)
"plane!"

In Ruby and Elixir you have range types where you have -1 representing the last element. ("snakes on a plane!" |> String.slice(-6, -1) is not something you can do in Elixir because the 3rd argument isn't an index, but the length of the slice to take).

eterps commented 4 years ago

I'm starting to think that Elixir's variant that specifies the length is the least confusing (i.e.: slice(string, start, len)):

iex(1)> "freeby" |> String.slice(-2, 2)
"by"
iex(2)> "snakes on a plane!" |> String.slice(-6, 6)
"plane!"
iex(3)> "snakes on a plane!" |> String.slice(-6, 5)
"plane"
lpil commented 4 years ago

Thanks for doing this research. I think that last example is clear

So would that be slice(str, from: -8, up_to: 4) with labels? From memory I think this matches the list take/drop functions.

eterps commented 4 years ago

Thanks for doing this research. I think that last example is clear

:+1:

So would that be slice(str, from: -8, up_to: 4) with labels? From memory I think this matches the list take/drop functions.

In the list take/drop functions the labels have a different meaning:

// take(from list: List(a), up_to n: Int) -> List(a)
take(from: task_list, up_to: 4)
// Or:
task_list |> take(up_to: 4)

For string.slice I would suggest:

invoice_label |> slice(at_index: 6, length: 8)
// Or:
slice(from: invoice_label, at_index: 6, length: 8)

Then it's unambiguous and the from: is used similar to the list take/drop functions.

What do you think?

lpil commented 4 years ago

Looks good. What about length -> up_to? As you may not always get that many graphemes

eterps commented 4 years ago

Looks good. What about length -> up_to? As you may not always get that many graphemes

I agree, that would also make it more consistent with the up_to label used in other functions.

invoice_label |> slice(at_index: 6, up_to: 8)

I do think some people could interpret it as in index and that length would make that less ambiguous, but to me personally up_to sounds clear enough :+1:

lpil commented 4 years ago

Good point, let's go for length then.