Hacker News new | past | comments | ask | show | jobs | submit login
Zip Function Solution: Haskell, Elixir, JavaScript, Python, Vlang (kevin-da-silva.medium.com)
44 points by Tozen on Nov 10, 2022 | hide | past | favorite | 65 comments



It's a good learning exercise.

Of course, don't use your own hand crafted version in Python, since one is provided with the language and it is better than the one you will likely come up with.

Still, it's interesting because it's simple. So starting from the intro of this article, if you want to go further, try to do the following to match up __builtins__.zip:

- make it lazy (yield would work)

- make it work with any iterable (iter() + StopIteration would work)

- put some type int and a docstring (so that help() gives you informations)

- accept any number of iterables (variadic parameters would work)

- make a second version that accepts a fill value (like itertools.zip_longest)

This will sharpen your python skills, and also will make you appreciate even better how much you get for free with the stdlib (not to mention __builtins__.zip is in C, so it's fast).


It's worth noting that `map` is variadic and acts as an nary `zipWith`. I didn't realize that for a while.

    >>> from operator import `add`
    >>> list(map(add, range(5), range(10, 15)))
    [10, 12, 14, 16, 18]
    >>> add3 = lambda x,y,z: x+y+z
    >>> list(map(add3, range(5), range(10, 15), range(100, 105)))
    [110, 113, 116, 119, 122]


> will make you appreciate even better how much you get for free

This is unironically the reason I come back to Python again and again. The language itself is not my favorite: it 'should' be different. But my productivity with it is frankly unmatched.

The "zip" example is an excellent case study. The actual "zip" is by default lazy, it accepts arbitrary iterators, etc. It's the "correct" solution, unlike the one presented in the article, and I have it at my fingertips, in the standard library.


> This is unironically the reason

Can I ask an off topic question? Where did this sudden popular use of “unironically” come from? I don’t get it. Why would it be ironic?

It reminds me of the sudden rise of “literally” a decade or so ago.


"Unironically" here means "seriously", "literally", "actually", etc. It's basically a marker that the speaker is speaking straightly and plainly, without trying to make a joke.

Yes, it's not really antonymous to "ironically" (in the narrower sense of this word) in this context.


I think it's fairly literal, there's a whole brand of humor around pretending to like things that you actually like. "I like C because of all the undefined behavior".

But this is also how word meanings shift over time. Someone will see someone else using that word and not know what it means, but then start using it, etc.


In reality most of these are really simple, the big question is how to handle end of iteration across iterators e.g. given zip(a, b) where a is shorter than b, on the last iteration will b get updated or not?


In Python an iterator is simply an object that implements the __next__ method, and raises StopIteration to signal the end of the iteration.

It's not hard to implement just about any protocol on top of that abstraction, e.g. you have itertools.zip_longest()


I know what an iterator is. I think you missed the point.


Would you like to explain that further?


Progressing an iterator is a side-effect (a mutation).

Depending how the last iteration is handled, zip(a, b) and zip(b, a) with iterators of different sizes can have different behaviours: if a is shorter than b, a naïve implementation would update b in the zip(b, a) case, but not in the zip(a, b) case.


There is no unique solution to that problem since sometime you want consumption, and sometimes not, for one or more of the parameters.

__builtins__.zip chose to consume iterators in the order they are passed as a parameter, and therefor end up with an assymetric consumption.

That's why itertools.tee exists, so that we can chose case by case.


That reminds me of a lovely paper "How to Add Laziness to a Strict Language Without Even Being Odd" by Wadler and MacQuenn. They use "map" as their running example, but the problem is basically the same.


zip is generally implemented as a functional concept, where the iterators take values from the backing iterable structure "by const &". You can implement a custom mutating zip operation on top of the iterator protocol.


I believe masklinn is talking about whether `zip` consumes an extra element from the longer iterator on the last iteration, when the shorter iterator raises StopIteration. And the behavior might vary depending on the order of the arguments.

    def a():
      yield "a1"

    def b():
      yield "b1"
      yield "b2"

    x,y = a(),b()
    zip(x,y) # => [("a1","b1")]
    next(x) # => raise StopIteration
    next(y) # => "b2"

    x,y = a(),b()
    zip(y,x) # => [("b1","a1")]
    next(x) # => raise StopIteration
    next(y) # => raise StopIteration


This is the problem with python's conflation of iterable and iterator, and maybe why so many programmers don't understand the difference when they pick up Java.


> This is the problem with python's conflation of iterable and iterator

It really is not. It’s just a natural outcome of imperative langages. If operations can have side-effects, then combinator behaviour will affect those side-effects.

You could do the same with iterables with side-effecting iterations.

It’s also perfectly logical to be able to iterate an iterator.


Common Lisp version, works with an arbitrary number of lists and automatically trims the result to the length of the shortest list.

    (defun zip (&rest lists)
      (apply #'mapcar #'list lists))
Example:

    CL-USER> (zip '(1 2 3) '(4 5 6 7 8) '(a b c d))
    ((1 4 A) (2 5 B) (3 6 C))


Limited by the implementation specific max number of arguments.


A simpler (IMHO) Python version, plus lazy and working on any iterable. Also, it's quite easy to adapt it to work on any number of iterables.

    def myzip(x, y):
        x, y = iter(x), iter(y)
        try:
            while True:
                yield next(x), next(y)
        except StopIteration:
            return


I was looking for a way to implement this more pythonically. And this looks great. But it made me think that if x and y are of different lengths, the last iteration might consume an element out of the longer iterator.

So I tried it with the builtin `zip(a,b)`, and indeed, if a is shorter, `zip(a,b)` consumes the same number of elements from both a and b. But if a is longer, then `zip(a,b)` consumes one extra element from a. I don't know if this behavior is well defined, that is whether I can rely on this, or the order of evaluation of the arguments of a function is up to the interpreter/compiler, and may change.

masklinn wrote a comment in another thread about this that i didn't understand until I saw your implementation.


    def myzip(*seqs):
        next_yield = [None] * len(seqs)
        its = [iter(seq) for seq in seqs]
        should_go = True

        while should_go:
            for index, it in enumerate(its):
                try:
                    next_yield[index] = next(it)
                except StopIteration:
                    should_go = False

            if should_go:
                yield tuple(next_yield)
It consumes exactly the same number of elements from all sequences, unless a non-StopIteration exception occurs during the iteration.


I‘m not good in JavaScript, but reading the code, I wonder if this implementation doesn’t have an issue with truthiness in the second array. For example if you zip([true,true],[false,false]).

The minimum length approach seams to be the better one. (Without having it run anywhere.)


That was my quick reaction as well... I think it would need something like the python example below, where you first check the lengths of the two arrays/lists, and then iterate over that, and not directly over the length of the first list.

    iter_limit = Math.min(ls1.length, ls2.length);
    for(let i = 0; i < iter_limit; i++)...


  function zip(arr_a, arr_b) {
    const len = arr_a.length < arr_b.length ? arr_a.length : arr_b.length;

    return arr_a.slice(0, len).map((el, idx) => [el, arr_b[idx]]);
  }


Yes, the JavaScript implementation doesn't works if the second array contains 'false', 'null', 'undefined', '0' or 'NaN'.

Here is my implementation of the 'zip' function in modern JavaScript. It is similar to the Vlang version, but it is generic over the number of arrays :

  const zip = (...arrays) => {
    const length = Math.min(...arrays.map((array) => array.length));
    return Array.from({ length }, (_, i) => arrays.map((array) => array[i]));
  };


Generally in Python it's much better to use list comprehensions when possible, it offers significant speedups usually:

  def zip(ls1, ls2):
      return [(ls1[i], ls2[i]) for i in range(min(len(ls1), len(ls2)))]
And indeed you could even use a generator instead:

  def zip(ls1, ls2):
      return ((ls1[i], ls2[i]) for i in range(min(len(ls1), len(ls2))))


The inclusion of Vlang is interesting. I remember that it was created together with Volt, a multi-service messenger app that never delivered. Is the language any good, and are there any advantages over more established esoteric systems languages like D, Nim, or Zig?


From a pure language perspective, not taking into account infrastructure and implementation, Vlang is currently my favorite language. It has pretty much the syntax and semantics I want in a statically compiled, strongly typed language. However, there are still too many features of the current implementations that are in reality not yet implemented or too experimental. It's still not ready for production and larger projects.

I'd say the languages you mention are substitutes according to the following mapping:

    C++    => D
    Python => Nim
    C      => Zig
    Go     => V


V promised a lot of stuff as ready but has undelivered most of them and people lost trust. If V was truthful like Zig, it would be able to create a foundation. Also a programming language will never be able to substitute another one if it is not able to call its vast number of libraries.


I agree, the table refers to the intention of the language developers and how the languages feel, it's not about real substitution. For example, D was explicitly designed as a better version of C++ and also allows you to use C++ libraries directly, and Zig is explicitly intended as a modern substitute for C.

As for V, it delivers quite well for a language at such an early stage and has a growing community. IMHO, it's a great language.


Still would not use V myself, its the prime example of overmarketing of impossible features, silencing of critics online etc


Since you are comparing Zig with V:

> V promised a lot of stuff as ready but has undelivered most of them

This is wrong. V has an online playground, a package manager, hot code reloading and a REPL whereas Zig doesn't even though Zig is twice as old.

> If V was truthful like Zig, it would be able to create a foundation

Raising money doesn't lead to adoption, though. By this logic, C isn't truthful either since it doesn't have a foundation.

> Also a programming language will never be able to substitute another one if it is not able to call its vast number of libraries

How is this relevant? Both Zig and V can call C code and leverage C's ecosystem of libraries.


This comment started a hellish flamewar. Please don't start programming language flamewars on HN. Last thing we need here.

https://news.ycombinator.com/newsguidelines.html


[flagged]


It might be better now, but these posts from a few years ago painted a very different picture:

https://xeiaso.net/blog/v-vaporware-2019-06-23

https://xeiaso.net/blog/v-vvork-in-progress-2020-01-03

https://xeiaso.net/blog/vlang-update-2020-06-17

They were doing very level-headed reviews of the language at that time, found that the claims were mostly unsubstantiated, and then got blocked by the V team.

That did not inspire confidence at all, as you might imagine. I can completely understand OP when they are saying "V promised a lot of stuff as ready but has undelivered most of them and people lost trust." -- that was my view too.

-----

Again, it might be better now. The V team might have delivered in the meantime. But I can definitely see where OP is coming from.


[flagged]


Things might be better now, but the previous false advertisement makes it really hard to tell. For example, vlang.io claims no undefined behaviour¹, but with

    struct Foo {
        bar int
    }
    println(&Foo(voidptr(some_address_here)))
it seems to read whatever memory is at that address (which is UB in C, and V compiles to C, so...).

¹it says some overflowing can still result in UB, but that's not what's happening here; also this warning was only added in July.


I am not a "sleeper account". I posted a very calm and charitable take on my view, in it explaining that I understand OP's view. Nowhere did I smear the language, or troll, or do anything of the sort.

You on the other hand immediately label me as a sleeper account and a troll, for only mentioning a few links to a webpage. Although I can understand being defensive by default if you get trolled a lot, I don't think this is a good way to win people over. It certainly is off-putting to me.

-----

But anyway, content related: did Xe/Xena/Christine lie in their blog posts at that time? I've read other blog posts by them and those generally seem to be level headed, well written blog posts -- not spam or anything.

Next to checking out the Vlang's website, those blog posts were my only exposure to the language at the time -- as you can imagine for a new language.

-----

>> ...and then got blocked by the V team.

> This appears to be more trolling, and just distortions or lies, as a setup to unload more spam links and misinformation.

Is the image in the tweet in the last post not correct? That Xe got blocked by @v_language? Or is that not the official Twitter account?

You have to remember, I'm just an outside observer -- and all I see is a person testing the claims a language does, finding they are mostly unsubstantiated, and getting blocked in the process.


[flagged]


All I can say is, I'm not that poster. The names being similar is a coincidence. This being mostly an anonymous forum however, I don't have anything to prove it to you. Maybe my post history can serve as a small argument, although that can be faked/bought/hacked too.

I think it would be better to engage in open, sincere, and civil discourse about the language -- instead of labelling people that raise questions or concerns as trolls with sleeper accounts.


[flagged]


We don't allow single-purpose accounts on HN, nor (to widen the scope slightly) accounts that are primarily obsessed with one agenda. That's incompatible with intellectual curiosity, which is the thing this site exists for.

Since your account seems clearly in that category, I need to ask you to stop posting about this, not just now but in the future. That's not taking a side about Vlang (I have no idea!) - it's just about the intended use of HN.

https://news.ycombinator.com/newsguidelines.html


> Vlang has constantly over promised and under delivered, most of the features they advertise don't work or works in a very limited scope.

This is the 3rd time in this thread this claim is getting repeated. From my previous comment:

"V has an online playground, a package manager, hot code reloading and a REPL whereas Zig doesn't even though Zig is twice as old."

If you can't provide examples of undelivered features, I'm going to have to say you're spreading lies.

Also literally in the HN thread you shared the author of V refuted the claims from the article.


> ...successfully pushed a potential new user away...

1) You are "projecting", what is your purpose and agenda.

That is your intent, as an obvious troll (who masquerades as a Rust advocate), that probably has multiple accounts or working in a concerted effort from an organization of a competing language.

2) Your agenda is very clear.

It is mainly attacks, flames, and unloading of spam on any posts or threads which mention or are concerning Vlang.

3) Your troll account should have been banned a while ago.

All of this is very clear by your disruptive flame war comment history, for all to see.

> Vlang warriors...

So PufPufPuf and jonathanstrange are also "Vlang warriors", for just casually mentioning and saying something positive about Vlang on a thread about it and multiple languages?

What actually is happening, is the mere mention of Vlang can cause various rivals and competitors to engage with troll accounts to spam the name of their competing language (in this case Zig) and unload massive amounts of misinformation, propaganda, lies, and negative disruption on threads.

Notice how it is never initiated in the opposite direction. It is nearly always advocates of specific rival languages doing this, though they kind of and sloppily try to hide their affiliation, but often slip up as apparently they can't help themselves. Their activity consists of attacking or bashing Vlang (to include spamming the same negative propaganda links), when it is positively mentioned. This is very clear from HN's post history.

To include, this spamming or trolling activity by various evangelists or paid advocates of a certain language has gone over the top on the HN site, and is now on the radar of moderators (who have mentioning it).


Please don't post in the flamewar style to HN. Regardless of how wrong someone is or you feel they are, it's not what this site is for, and it destroys what it is for.

You did it a whole bunch in this thread, unfortunately. If you wouldn't mind reviewing https://news.ycombinator.com/newsguidelines.html and taking the intended spirit of the site more to heart, we'd be grateful.


[flagged]


Haha, alright man. Still with the alt/sleeper account. Cool. I'm just some guy from the Netherlands trying to get a calm conversation going. I don't feel like any of my posts have been in any way inflammatory.

You seem way too defensive to have any useful conversation with, so I won't bother any more. Good luck with vlang!


To give you some background on why your original post might've been considered inflammatory - the author of the blog you shared has openly said "V is something that should be ignored until it dies into obscurity":

https://news.ycombinator.com/item?id=27442724

I wouldn't call the author an objective reviewer of the language.

Also even in this thread there were people spreading lies about the language:

https://news.ycombinator.com/item?id=33544205

https://news.ycombinator.com/item?id=33544576

who have a history of spreading lies about V that goes back years:

https://news.ycombinator.com/item?id=20765490

My advice is - check out the language and make up your own mind. In the end this is a tool, not a football club.


Hey, thanks for the civil response!

> the author of the blog you shared has openly said "V is something that should be ignored until it dies into obscurity":

I didn't know this -- that's very harsh, and I don't agree at all. I see that that comment was made after the reviewing blog posts, however. I can get that the relation between V and them has soured.

> Also even in this thread there were people spreading lies about the language: https://news.ycombinator.com/item?id=33544205

That's the one I initially replied to, sharing my view as an outside observer. It would probably serve V well to handle such popular blog posts in some way. Like put up a blog post refuting the points in the article, and then link to that or something. Something to undo the damage to the language's image by those posts, and (re-)inspire confidence.

The other way is probably to be "so good they can't ignore you". That would be great, always good to have more great tools.

> My advice is - check out the language and make up your own mind. In the end this is a tool, not a football club.

I completely agree -- and people should try it for themselves. I guess my point is mostly that if the community's exposure to a language is those blog posts, they won't be inclined to try it. And if those blog posts don't get handled in a graceful and confidence inspiring way, the community won't "update" its view of the language.


By the way usually when a hit piece about V starts circulating on HN, the author of V takes the time to refute the claims in the comments section:

https://news.ycombinator.com/item?id=31793554

(Ctrl+F amedvednikov)

But I agree 100% - having a blog post that refutes all the claims in one place would be very useful.


Ah, that's good to know! Will check it out.


[flagged]


Hey Tozen, are you alright? It might be good for you to take a step back and relax a bit.

I have been nothing but civil and open-minded, I have not smeared vlang in any way, I have merely been asking questions as an outside observer.

You, on the other hand, have been aggressively defensive, and have been projecting an almost paranoid "everything and everyone is against vlang" attitude. I almost start thinking that you are the one trolling me.

If not, I sincerely hope you will take a step back and assess the situation and yourself -- and try to relax a bit. No single programming language is worth getting this wound up about.


Please do not post flamewar comments to HN, regardless of how wrong someone is or you feel they are. It's not what this site is for, and it destroys what it is for.

If you wouldn't mind reviewing https://news.ycombinator.com/newsguidelines.html and taking the intended spirit of the site more to heart, we'd be grateful.


D and Nim are great mature languages. If you think they're suitable for the domain you're working on and reach out for either of them, you're not gonna go wrong. They have large communities and you'll easily find answers to your questions.

I wouldn't call Zig established, though. Zig's compiler still crashes on valid code and compared to V it still doesn't have an online playground, a package manager, hot code reloading, a REPL, etc. even though it's twice as old.

V's syntax is similar to Go. However under the hood V differs from Go. For example:

- V has compile-time memory management similar to the Lobster programming language.

- V has zero-cost interoperability with C.

Check it out and decide for yourself. If you already know Go, you can learn V in an afternoon.


Nim is very fast but also very expressive. Think of a compiled Python, but with Rust-like safety.


In TCL, zip functionality is built into the foreach loop.

     foreach x $list1 y $list2 {
         lappend result [Myfunc $x $y]
     }

To directly return a list of outputs without having to append to a list, TCL also provides lmap which works the same way:

     lmap x $list1 y $list2 {Myfunc $x $y}

But wait there's more! Foreach and lmap also support taking more than one element from the list each time:

     lmap {x y} $list1 {Myfunc $x $y}


It's gauche to flaunt the power of the gods among mortals.


For Python, I’d prefer a generator over appending. Less imperative style-wise and faster too I think.


Also working with iterables rather than requiring sequence.

Aka all the things the builtin does (well it also supports zipping an arbitrary number of iterables).


Indeed. FWIW I meant ‘comprehension, rather than ‘generator’, but hey, both would work


FP is nice, but logic programming also has its niceties.

  (define (zipo a b z)
    (conde ((nullo a) (nullo z))
           ((nullo b) (pairo a) (nullo z))
           ((fresh (ahead atail bhead btail ztail)
              (conso ahead atail a)
              (conso bhead btail b)
              (conso (list ahead bhead) ztail z)
              (zipo atail btail ztail)))))

  (run* (q) (zipo '(1 2 3) '(a b c) q))
  => (((1 a) (2 b) (3 c)))
  (run* (q) (zipo '(1 2 3) q '((1 a) (2 b) (3 c)))
  => ((a b c . _.0))


Interestingly, the post says "JavaScript [...] has tail call issues", but at least the Elixir algorithm is not tail recursive and will blow up with inputs that are too big.


Make it simple, THEN make it fast/efficient.

Here's a tail-recursive function. To be honest I would have started writing this version first, the acc pattern is ubiquitous. Sorry for the mistakes, I'm on mobile:

    def zip(a, b) do
      zip(a, b, [])
    end

    def zip([], _, acc), do: acc

    def zip(_, [], acc), do: acc

    def zip([a | a_rest], [b | b_rest], acc) do
      zip(a_rest, b_rest, [{a,b}|acc])
    end
Also, there's Enum.zip/2 in the standard library, with the bonus that it works with any Enumerable type (lists, maps, lazy streams, etc,)


Sure, but if we want to highlight other languages' lack of tail call support, maybe we should showcase that support in those languages that do support it. :)


The JavaScript one is incorrect. It should use the length of the 2nd list rather than stop at the first falsy value in it.

That's where I stopped reading. Even going in, I wondered doesn't each of these languages already have a function in stdlib or a de-facto package with it?


My solution for Javascript:

  function zip(arr_a, arr_b) {
    const len = arr_a.length < arr_b.length ? arr_a.length : arr_b.length;

    return arr_a.slice(0, len).map((el, idx) => [el, arr_b[idx]]);
  }


zip is useful to have as a library function in a Lisp dialect in spite of being easily able to compose it with mapcar and list.

It comes up fairly often in macros. E.g. you have gens, a list of gensyms, and exprs, a list of expressions, and body, a list of forms that refer to the gensyms:

  `(let ,(zip gens exprs) ,@body)


In JavaScript:

  let zip = (as,bs) => as.slice(0, Math.min(as.length,bs.length)).map((a,i) => [a,bs[i]]);


q:

  {(,')over min[count each x]#'x}
k4:

  {,'/(&/#:'x)#'x}




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: