Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make array.repeat public #3978

Closed
wants to merge 1 commit into from
Closed

Conversation

JustForFun88
Copy link
Contributor

The repeat function is useful not only for multiplication, but also for creating an array from a single value.

User-facing API

#assert.eq(
  array.repeat("A", 2),
  ("A", "A"),
)

Analogues in other languages

@PgBiel
Copy link
Contributor

PgBiel commented Apr 22, 2024

I'm confused, isn't this what ("A",) * 2 calls? It produces ("A", "A") as well.
Perhaps you misinterpreted what mul was alluding to in this context?

@JustForFun88
Copy link
Contributor Author

JustForFun88 commented Apr 22, 2024

I'm confused, isn't this what ("A",) * 2 calls? It produces ("A", "A") as well. Perhaps you misinterpreted what mul was alluding to in this context?

Yes you are right. This is the same function that mul used when called on an array. I haven't added any new function. I just made a previously private function public and now it can be called via an explicit repeat. In my opinion, using an explicit repeat is better when reading a function fluently, since the eye does not stumble over (val,)

@Enivex
Copy link
Collaborator

Enivex commented Apr 22, 2024

Do we really need all these methods? I feel like they're making the scripting unnecessarily complicated.

@JustForFun88
Copy link
Contributor Author

Do we really need all these methods? I feel like they're making the scripting unnecessarily complicated.

These are just a few features and they make scripting easier, not harder. Many languages, especially functional ones, contain them. The presence of these functions allows you to create chains of methods without having to break the chain.

In addition, this particular function makes reading scripts easier, not more difficult. Agree, for example, (1, 1).append((2, 2)) is a much more explicit notation than (1, 1) + (2, 2) == (3, 3) or (1, 1, 2, 2)?. Or is repeat(1, 3) == (1, 1, 1) more readable rather than (1,) * 3 == (1, 1, 1) or (3,)?. In addition, a notation like (1,) * 3 looks uglier. It is also worth noting that functions such as append and repeat are so common that no one will doubt what they do, while the use of the + and * operators is not universal and differs from language to language. For example, I would expect behavior like in Matlab, since it is a mathematical layout language.

@Enivex
Copy link
Collaborator

Enivex commented Apr 23, 2024

Many languages, especially functional ones, contain them.

Typst is not a general purpose language

@PgBiel
Copy link
Contributor

PgBiel commented Apr 23, 2024

Perhaps we can find a compromise by making a package with those functions? That way all the other ones in the linked references can be added as well.

@JustForFun88
Copy link
Contributor Author

JustForFun88 commented Apr 24, 2024

Perhaps we can find a compromise by making a package with those functions? That way all the other ones in the linked references can be added as well.

I’m not sure how you’re going to turn the built-in methods into a separate package. After all, Typst is not Rust and it has no traits. Here, either you make full-fledged array methods for chaining, or you stay with a procedural language.

@laurmaedje
Copy link
Member

In addition, this particular function makes reading scripts easier, not more difficult. Agree, for example, (1, 1).append((2, 2)) is a much more explicit notation than (1, 1) + (2, 2) == (3, 3) or (1, 1, 2, 2)?. Or is repeat(1, 3) == (1, 1, 1) more readable rather than (1,) * 3 == (1, 1, 1) or (3,)?. In addition, a notation like (1,) * 3 looks uglier.

I think this is rather subjective. I personally find (1, 1) + (2, 2) easier to read then (1, 1).append((2, 2)) and (1,) * 3 also not particularly bad. With repeat, it is not 100% clear to me whether it repeats one element N times, or an array N times. The chaining argument doesn't really make sense for array.repeat since it's an associated static function rather than a method.

@JustForFun88
Copy link
Contributor Author

I think this is rather subjective. I personally find (1, 1) + (2, 2) easier to read then (1, 1).append((2, 2)) and (1,) * 3 also not particularly bad. With repeat, it is not 100% clear to me whether it repeats one element N times, or an array N times. The chaining argument doesn't really make sense for array.repeat since it's an associated static function rather than a method.

Well, I was thinking of using expressions like: .append(repeat(val, times)), which seems easier to read and looks nicer than .append((val,) * times).

@Enivex
Copy link
Collaborator

Enivex commented Apr 30, 2024

I think this is rather subjective. I personally find (1, 1) + (2, 2) easier to read then (1, 1).append((2, 2)) and (1,) * 3 also not particularly bad. With repeat, it is not 100% clear to me whether it repeats one element N times, or an array N times. The chaining argument doesn't really make sense for array.repeat since it's an associated static function rather than a method.

Well, I was thinking of using expressions like: .append(repeat(val, times)), which seems easier to read and looks nicer than .append((val,) * times).

Again, this is subjective

@JustForFun88
Copy link
Contributor Author

JustForFun88 commented May 1, 2024

Again, this is subjective

Just think about whether this is taking too seriously just one of the dozens of types in Typst. Adding this method does not change the language itself and simply improves ergonomics by adding a function that has analogues in dozens of languages.

At the same time, you are referring to a feature that is not even properly documented and is simply mentioned in passing in the documentation. I didn’t know that this was possible until you showed it here. And I think many people don’t immediately find out. You don't expect people to start reading documentation like a novel. They usually open it and quickly search for the desired method, sometimes simply by searching through keywords.

You can even look at Rust where there are dozens ways to match Result (map, map_err, map_or, map_or_else, ...)?

@Enivex
Copy link
Collaborator

Enivex commented May 1, 2024

Again, this is subjective

Just think about whether this is taking too seriously just one of the dozens of types in Typst. Adding this method does not change the language itself and simply improves ergonomics by adding a function that has analogues in dozens of languages.

No, I really don't think so. This is not about one type, or one method. It's a question of philosophy. Any single change made is obviously not going to change the language itself, but it's worth zooming out and looking at the big picture. What kind of language do we want to end up with?

There simply aren't "dozens of languages" occupying the same space as typst does.

At the same time, you are referring to a feature that is not even properly documented and is simply mentioned in passing in the documentation. I didn’t know that this was possible until you showed it here. And I think many people don’t immediately find out. You don't expect people to start reading documentation like a novel. They usually open it and quickly search for the desired method, sometimes simply by searching through keywords.

If your complaint is about lack of documentation, then that should be addressed by improving the documentation.

You can even look at Rust where there are dozens ways to match Result (map, map_err, map_or, map_or_else, ...)?

I don't see why Rust is relevant to this conversation?

@JustForFun88
Copy link
Contributor Author

Just think about whether this is taking too seriously just one of the dozens of types in Typst. Adding this method does not change the language itself and simply improves ergonomics by adding a function that has analogues in dozens of languages.

No, I really don't think so. This is not about one type, or one method. It's a question of philosophy. Any single change made is obviously not going to change the language itself, but it's worth zooming out and looking at the big picture. What kind of language do we want to end up with?

In my opinion, you are confusing the changes in the syntax of the language with adding a method to one single type out of a dozen types. In this particular case, the addition or absence of this method does not affect "What kind of language do we want to end up with?". Typst already has "methods" and there are "method chains". The decision to add "methods" as a language construct has already been made.

There simply aren't "dozens of languages" occupying the same space as typst does.

By dozens of languages I meant "dozens of languages" that have types like array and have "methods" as a language construct.

You can even look at Rust where there are dozens ways to match Result (map, map_err, map_or, map_or_else, ...)?

I don't see why Rust is relevant to this conversation?

Rust is an example of a language that has many methods that can be replaced using one construct - the match. It's the same with all other languages.

@Enivex
Copy link
Collaborator

Enivex commented May 1, 2024

In my opinion, you are confusing the changes in the syntax of the language with adding a method to one single type out of a dozen types. In this particular case, the addition or absence of this method does not affect "What kind of language do we want to end up with?". Typst already has "methods" and there are "method chains". The decision to add "methods" as a language construct has already been made.

I'm honestly not sure what you're talking about. My complaint is not methods in general. It's that having methods does not mean you have to add every single method under the sun to the language, especially when there are existing ways to do the same thing.

And I emphasize again that it's not about "one method" for "a single type". Obviously there will be more PRs in the future, and the discussion should be had early on.

Rust is an example of a language that has many methods that can be replaced using one construct - the match. It's the same with all other languages.

And rust is a general purpose language that honestly has very little in common with typst apart from being the language it's written in.

I'm afraid that there's too much focus on "nice to haves" for power users, to the detriment of how the language feels for the majority of users, most of which will have little (if any) exposure to programming.

@laurmaedje
Copy link
Member

I don't think it would necessarily be a problem to have this both as a method and with the operator (contains also exists next to the in operator). The method docs could somewhat aid discoverability of the operator.

However, I still have the concern I raised above: When seeing the name repeat, it is not clear to me whether it repeats one element N times, or an array N times, which I think is suboptimal, since both can be useful. The operator fairly easily adapts to both (either array * x or (item,) * x), while the repeat implemented here wouldn't support the array * x case). If you look at Rust, its repeat actually repeats the whole slice and I think that might actually be the more common usage of the term.

@JustForFun88
Copy link
Contributor Author

JustForFun88 commented May 6, 2024

However, I still have the concern I raised above: When seeing the name repeat, it is not clear to me whether it repeats one element N times, or an array N times, which I think is suboptimal, since both can be useful. The operator fairly easily adapts to both (either array * x or (item,) * x), while the repeat implemented here wouldn't support the array * x case).

I don't quite understand you. This method takes value and make an array of length N with that value. If we pass a number, the method will return an array containing the number, if we pass an array, then the method will return an array of arrays:

#array.repeat((1,)  3) == ((1,), (1,), (1,))
#array.repeat((1, 2)  3) == ((1, 2), (1, 2), (1, 2))
#array.repeat("a", 4) == ("a", "a", "a", "a")

@laurmaedje
Copy link
Member

What I mean is that the method could also be understood as (2, 3).repeat(3) == (2, 3, 2, 3, 2, 3), which is what it does in Rust. So it can either be (any, int) => array or (array, int) => array, both with somewhat different semantics.

@JustForFun88
Copy link
Contributor Author

What I mean is that the method could also be understood as (2, 3).repeat(3) == (2, 3, 2, 3, 2, 3), which is what it does in Rust. So it can either be (any, int) => array or (array, int) => array, both with somewhat different semantics.

One might agree that one might think that (2, 3).repeat(3) is equal to (2, 3, 2, 3, 2, 3). But that's exactly what the documentation is for, isn't it? As for Rust, you are probably describing cycle, since repeat behaves the same way as I described, i.e. repeats the passed value:

fn main() {
    use std::iter;

    let array = iter::repeat(4).take(5).collect::<Vec<_>>();

    assert_eq!(array, [4, 4, 4, 4, 4]);

    let array_of_arrays = iter::repeat([1, 2]).take(5).collect::<Vec<_>>();

    assert_eq!(array_of_arrays, [[1, 2], [1, 2], [1, 2], [1, 2], [1, 2]]);
}

@laurmaedje
Copy link
Member

I was referring to slice::repeat rather than iter::repeat in Rust.

But that's exactly what the documentation is for, isn't it?

To an extent, yes, that is what documentation is for. If the method would add new functionality I would also consider this less of a problem, but the fact that it is duplicate functionality and slightly amibigious in what it does leaves the motivation for adding it a bit thin in my opinion. Especially since the existing multiplication accomodates both use cases of repeat (single item & array repeat) fairly well and is thus more flexible than the method would be.

@Myriad-Dreamin
Copy link
Contributor

Myriad-Dreamin commented May 8, 2024

In my opinion and from my collected voice, the repeating array by multiply is much less clear than repeating array by repeat method. The same thing happens on the length type as well, where you have to convert from float by multiplying a length unit. Most people don't really get that they can do something with * as they are thinking in methods.

@laurmaedje
Copy link
Member

I think that's more of a documentation problem. It is stated in the array reference, but people don't read the text at the top. Python also uses multiplication for lists and I think it works fine there. In combination with the unclarity of whether repeat is array.repeat(elem, n) == (elem,) * n or myarray.repeat(n) == myarray * n I'm not convinced of adding this method. To aid discoverability, we could add a hint when trying to call .repeat on an array, explaining to use multiplication instead.

@laurmaedje laurmaedje closed this May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants