-
Notifications
You must be signed in to change notification settings - Fork 773
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
Seq.zip
, Seq.map2
etc behave different from List.zip
, List.map2
, Array.zip
etc w.r.t. raising for different lengths
#14121
Comments
I guess the main difference is that seq can be lazy/a computation, and we may not know its length on advance, so to check it we may need to enumerate it. I would say it makes sense for seq not to throw, since it's quite different from the collection types such as list or array |
@vzarytovskii thanks! Note that we're talking here specifically about pairwise enumerations here (zip, mapi2, map2, iter2 etc). Both sequences are advanced at the same time. So by the time one is exhausted, we know from the other one (through the paired call to In other words, we can throw without having to enumerate the whole sequence, we would throw only if the two
But then, why does it throw in other scenarios where exhaustion comes into play? It seems inconsistent. |
Altering this behavior would be a breaking change. I like the python like behaivour of not throwing things which make List.zip unusable in many cases. Compare to the slice operations that are safe now. |
Yeah, we can do the check while enumerating it, however will unlikely change the behaviour, since it will be a huge breaking change. |
Oh, definitely. I understand it would be a breaking change. I should probably have opened this as a discussion instead. My main reason for asking, I guess, is to find out whether this was an oversight at the time (i.e. parity with other collections), or deliberate. As such, I'm considering what approach to take for
This is a good argument against throwing, for sure. I guess that, if there's a deterministic behavior that doesn't throw it should be preferred. Confusingly there's
Offering all three for each and every function seems a bit much. Also, the |
Wait, I get it for But it shouldn't evaluate anything, unless asked. I.e. |
This seems to be inconsistent with LINQ . where is does not throw for the examples show in this issue . #13207 . Is also not consistent with LINQ . |
@edgarfgp, yes, I'm working on that issue (unrelated to this, though). Note that |
This is by design, it's just always been this way, it's checked by tests in the repo. |
This is the rationale really. |
I understand the argument “it’s always been this way”, and that we can’t change it. I don’t understand the rationale, as none of my examples require iteration of the whole sequence to throw. In fact, already during the standard operation, all information is available to throw or not (mainly, the two or three booleans that know whether to continue). This is different from Haskell, btw, in which the order of arguments determines which of the Anyway, fair enough to close this out, I agree we shouldn’t change the defaults here. |
In cases where you apply a pairwise operation to two sequences, like
Seq.zip
, the behavior in F# Core is defined by the implementation ofSeq.map2
andList.map2
and the like. The behavior between collection types is different, however.Array.zip
andList.zip
throw anArgumentException
whenever the sequences have differing lengthsSeq.zip
on the other hand doesn't.I doubt this behavior can be changed (backwards compatibility), but I do think it is a bug/oversight or whatchamacallit. I'm currently working on implementing and extending
TaskSeq
, based on @dsyme's original code from this repo and raised this as a question to myself: fsprojects/FSharp.Control.TaskSeq#32. Then I figured, let's broaden the discussion scope ;).Repro steps
Also, this is quite weird:
Expected behavior
The same behavior for all collection types.
Actual behavior
Functions like
Seq.map2
,Seq.map3
,Seq.mapi2
,Seq.zip
do not raise anArgumentException
when the sizes are different. However, the implementations do read past the end of the sequence and even if false, read the next item of the paired sequence as well (seeMapEnumerator
code here). In other words, the information whether one or both sequences are exhausted is available.Known workarounds
In lazily evaluated sequences, the only workaround is to "roll your own". Easy enough, but still. Alternatively, you could, of course, cache the sequences as an eager sequence like
List
orArray
.Related information
I did try to find a motivation for this behavior in the source code an online, but failed to do so. There's certainly an argument to be made for not raising an exception, but then one would expect that to be the case for all collection types.
Perhaps there's something with lazy sequences that suggest not raising exceptions in general. But something like
[1..3] |> Seq.take 4
raises (not immediately, but when iterating over the sequence), in other words, it does not seem to be a taboo.The text was updated successfully, but these errors were encountered: