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

Python style List comprehensions #283

Closed
joe-conigliaro opened this issue Jun 20, 2019 · 30 comments
Closed

Python style List comprehensions #283

joe-conigliaro opened this issue Jun 20, 2019 · 30 comments
Labels
Feature/Enhancement Request This issue is made to request a feature or an enhancement to an existing one.

Comments

@joe-conigliaro
Copy link
Member

Have you thought supporting list comprehensions like in python?

@medvednikov
Copy link
Member

I think it goes against V's philosophy of "only one way to do it". Do you have an example of what you miss?

@joe-conigliaro
Copy link
Member Author

It's not that I miss them that much it's just they are kind of cool. You can really cut down on code verbosity turn a 5-6liner into one line. But v is similar to go in that i's a verbose language.

@medvednikov
Copy link
Member

Well it's a bit less verbose than Go, but yes, saving keystrokes is not its goal.

Can you post a simple example of what you'd like to do? Perhaps it can be done with the upcoming map/filter functions.

@joe-conigliaro
Copy link
Member Author

joe-conigliaro commented Jun 20, 2019

Here are some examples

Get even numbers from array

numbers := [1, 2 , 3, 4, 5, 6, 7, 8, 9, 10]
evenNumbers := [num%2 == 0 for num in numbers]
// Output: [2, 4, 6, 8, 10]
// Correction (thanks sonovice), Should be:
numbers := [1, 2 , 3, 4, 5, 6, 7, 8, 9, 10]
evenNumbers := [num for num in numbers if num%2 == 0]
// Output: [2, 4, 6, 8, 10]

Find common numbers in arrays:

arrayA = [1, 2, 3, 4, 5]
arrayB = [3, 4, 5, 6, 7]
common := [a for a in arrayA for b in arrayB if a == b]
// Output: [3, 4, 5]

@medvednikov
Copy link
Member

medvednikov commented Jun 20, 2019

even_numbers := [n for num in numbers if n % 2 == 0]
==>
even_numbers := numbers.filter(_ % 2 == 0)


common := [a for a in arrayA for b in arrayB if a == b]
==>
common := arrayA.filter(arrayB.contains(_))

@medvednikov
Copy link
Member

This is an upcoming feature, still not 100% decided on the syntax. But it's going to be something like this.

@joe-conigliaro
Copy link
Member Author

Awesome I like that! nice one!

@medvednikov
Copy link
Member

Cool :) I think it's a bit simpler and more readable than list comprehensions. I personally always found them a bit confusing.

@medvednikov
Copy link
Member

Actually the second one is going to be

common := [a for a in arrayA for b in arrayB if a == b]
==>
common := arrayA.filter(_ in arrayB)

@joe-conigliaro
Copy link
Member Author

ahh yeah we already have in for arrays :)

@sonovice
Copy link

The even numbers example gets it wrong. In python the output of the comprehension would be [False, True, False, True,...]

Just my 2 cents

@joe-conigliaro
Copy link
Member Author

joe-conigliaro commented Jun 20, 2019

The even numbers example gets it wrong. In python the output of the comprehension would be [False, True, False, True,...]

Just my 2 cents

Ohh yeah it should have been

numbers := [1, 2 , 3, 4, 5, 6, 7, 8, 9, 10]
evenNumbers := [num for num in numbers if num%2 == 0]
// Output: [2, 4, 6, 8, 10]

Thanks for pointing it out

@wilberton
Copy link

wilberton commented Jun 20, 2019

Out of interest, how is the 'in' operator implemented?
does arrayA.filter(_ in arrayB) do a linear search through arrayB for every element in arrayA?

@ntrel
Copy link
Contributor

ntrel commented Jun 20, 2019

Filter doesn't need to create an array, allocating is less efficient. Returning an iterator allows to compute elements as requested, and not all elements may be needed depending on later runtime tests, so this also can be more efficient. This is what D does for filter.

even_numbers := numbers.filter(_ % 2 == 0)

That lambda syntax is not very general, it doesn't allow referring to the first argument more than once, or multiple lambda arguments. _ as the only clue in an expression that it's a lambda is probably unique language syntax. The argument doesn't always come first: 1 / _.
I had thought of fn e! * e or fn x! + y! for very short general syntax, other sigils could work too. But we also need multiple statement function literals.

@ntrel
Copy link
Contributor

ntrel commented Jun 20, 2019

does (_ in arrayB) do a linear search

Yes, I don't think it's good to encourage this with an operator, see #277.

@medvednikov
Copy link
Member

medvednikov commented Jun 20, 2019

map and filter will be special methods handled by the compiler. This will allow this syntax and is needed anyway to optimize them.

I saw your concern about O(n) in, but it's pretty clear that checking whether an array contains an element is O(n).

I really want to have if p.token in [.plus, .minus, .div] { that's why I want in to exist :)

@ntrel
Copy link
Contributor

ntrel commented Jun 20, 2019

[.plus, .minus, .div].contains(p.token) does the job fine, and doesn't encourage programmers to choose a linear search by having special language syntax for it. It's not a common operation.

@medvednikov
Copy link
Member

How does in encourage linear search? How would you avoid linear without in?

@Undumendil
Copy link
Contributor

You could store a hash table or binary tree with element hashes for every list. It would be reasonable to have a special type for that since it requires additional memory.

@medvednikov
Copy link
Member

Hash table with fewer than ~100 elements is always going to be slower than linear search. I tested it. Well depends on the algorithm.

If you have an array, you search an array, if you have a map, you search a map. I don't think having in forces you to use arrays where maps would be more efficient.

@ntrel
Copy link
Contributor

ntrel commented Jun 20, 2019

map and filter will be special methods handled by the compiler. This will allow this syntax

Lambdas are needed for all kinds of generic algorithms, please don't make these two special cases.

and is needed anyway to optimize them.

Why is it needed, many other high performance languages use functions for map and filter. Making algorithms return lazy iterators is much better than hoping the compiler can optimize away the allocations (and unnecessary possible calculations for elements not read). That's the explicit, reliable way to write V code, otherwise the programmer has to be an expert on implementation optimizations to know whether the code will do what they intended.

@medvednikov
Copy link
Member

Why is it needed, many other high performance languages use functions for map and filter. Making algorithms return lazy iterators is much better than hoping the compiler can optimize away the allocations (and unnecessary possible calculations for elements not read).

Because I want to keep the language simple, and iterators are not simple.

@medvednikov
Copy link
Member

But I do see your points, I'll think about this.

I can't really focus on this right now. I have the big release in 2 days :)

@ntrel
Copy link
Contributor

ntrel commented Jun 20, 2019

How does in encourage linear search? How would you avoid linear without in?

Using linear search is fine. Building it into the language with special short syntax encourages programmers to reach for that instead of considering what other functions they might use.

@medvednikov
Copy link
Member

Ok, I can agree with that.

@ntrel
Copy link
Contributor

ntrel commented Jun 20, 2019

iterators are not simple

interface Iterable<E> {
  next() ?E
}

Seems pretty simple.

I'll think about this.

Thanks, good luck with the release :-)

@chanbakjsd chanbakjsd added the Feature/Enhancement Request This issue is made to request a feature or an enhancement to an existing one. label Jun 25, 2019
@olehmisar
Copy link

@medvednikov what about for expressions?

numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evenNumbers := for num in numbers {
    if num%2 == 0 {
        num
    } else {
        continue
    }
}

The else clause may be omitted because there is neither null nor undefined in V. So, the code would be simplified to

numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evenNumbers := for num in numbers {
    if num%2 == 0 { 
        num
    }
}

@medvednikov
Copy link
Member

@olegmisar

Yeah, maybe. Looks good to me.

@aguspiza
Copy link
Contributor

Seems pretty simple.

The complexity is not in the interface but in the implementation. Specially if you want easy to implement generators, i.e. yield keywork, dealing with state store/restore, etc

@ntrel
Copy link
Contributor

ntrel commented Jul 12, 2019

@aguspiza
I'm not asking for generators or yield. Not sure what you mean about state store/restore.

The beauty of iterators is that they are just structs with data and a next method, so if those features are well designed and safe then iterators automatically are too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature/Enhancement Request This issue is made to request a feature or an enhancement to an existing one.
Projects
None yet
Development

No branches or pull requests

9 participants