Open Happypig375 opened 3 years ago
Too much of a special case. If seq { 1..6 }
requires the seq
then set(seq { 1..6 })
should require the seq
.
I believe you misunderstood the request. In both seq { 1..6 }
and set(seq { 1..6 })
, the seq
is already optional today. The request is that seq
also be optional for { 1;2 }
as well.
Also worth noting that { 1..6 }
and seq { 1..6 }
does not produce the same code, as the former creates Operators.OperatorIntrinsics.RangeInt32(1, 1, 6)
while the latter creates Operators.CreateSequence(Operators.OperatorIntrinsics.RangeInt32(1, 1, 6))
.
Oh, { 1 .. 6 }
produces a range. That is interesting actually.
I think I like this, since it's pretty confusing that you don't need to do seq { x..y }
but you need to do seq { x; y }
. Since we can't remove the ability to do { x..y }
I'd be in favor of this provided there isn't a breaking change.
Since we can't remove the ability to do { x..y } I'd be in favor of this provided there isn't a breaking change.
TBH I think I'd rather force the addition of seq
through a warning or informational message.
There are three cases
{ x .. y }
{ 1; 2 }
{ if true then yield 1 }
and other computationsOnly (1) allows you to omit seq
today. This case snuck through in F# 2.0 and should really have always requried a seq
.
However I do like that set
and other future collection construction operations can omit the seq
without making those full computation expressions. That is, if the seq
is being immediately consumed eagerly into a collection construction then I'm happy to omit it. However we have no specific way of telling if that's the case
The reasons I like seq
are
As an aside (really a different issue) we should consider a .. b
in computation expressions more generally - currently these are only allowed in sequence, list, array expressions.
Approving the variation described here: https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088
@edgarfgp & @vzarytovskii regarding https://github.com/dotnet/fsharp/pull/17772 and https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088:
It seems like #1086 (which, incidentally, Don opened after https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088) would obviate the need for enabling set { x; y }
specifically, and I'd personally rather work towards[^1] some version of #1086 in the long run.
My understanding of https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088 is that, if anything, we should deprecate/discourage bare { start..finish }
.
This part of https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088 —
However I do like that
set
and other future collection construction operations can omit theseq
without making those full computation expressions. That is, if theseq
is being immediately consumed eagerly into a collection construction then I'm happy to omit it.
— seems like it would be effectively subsumed by #1086 instead.
let xs : Set<_> = [ x; y ]
and
let f (xs : Set<_>) = …
f [ x; y ]
and
let f (xs : Set<_>) = …
let xs = [ x; y ]
f xs
That would make it less compelling to enable the omission of seq
in
let xs = set { x; y }
since Don implies in
TBH I think I'd rather force the addition of
seq
through a warning or informational message.This case [
{ x .. y }
] snuck through in F# 2.0 and should really have always requried aseq
.
that we would not enable
let f (xs : Set<_>) = …
f { x; y }
or
let f (xs : _ seq) = …
let xs = { x; y }
f xs
and would instead still require seq { … }
in f (seq { x; y })
and let xs = seq { x; y }
for reasons like
the laziness is made obvious
it makes things distinct from object expressions and record expressions
it introduces people to computation expressions.
So #1086 would let us address more scenarios more consistently than would allowing the omission of seq
in set { x; y }
on its own.
It is worth remembering that #1086 would still require explicit seq { … }
for laziness (as opposed to [ … ]
) in both
let xs : _ seq = seq { x; y }
and
let f (xs : _ seq) = … in f (seq { x; y })
as mentioned in https://github.com/fsharp/fslang-suggestions/issues/1086#issuecomment-944240172:
The
[...]
syntax could only really be an eagerly-evaluated collection … it could only be used against collection types that define either an eager builder pattern or an eager create+Add
pattern.
That is, [ x; y ]
in something like
let xs : _ seq = [ x; y ]
or
let f (xs : _ seq) = …
f [ x; y ]
would still need to eagerly produce a list
, even if only for backwards compatibility, since such code is valid today and the expressions in [ … ]
could have side effects, etc.
We should also consider what directions we might take with #1044 and #1116 (see also https://github.com/fsharp/fslang-suggestions/issues/1317#issuecomment-2223481650 and https://github.com/fsharp/fslang-suggestions/issues/1317#issuecomment-2251126932).
For example, depending on how far we went in treating start..finish
as System.Range
(https://github.com/fsharp/fslang-suggestions/issues/1044#issuecomment-879137016), and how we enabled other constructs to support consuming System.Range
values, at least some current usages of { start..finish }
could potentially be replaced by start..finish
.
The syntax start..finish
is in fact already treated identically to { start..finish }
in some contexts:
let f start finish = for x in start..finish do ignore (float x ** float x)
let f start finish = for x in { start..finish } do ignore (float x ** float x)
But if (and I'm not saying we necessarily would or should) we went as far as C# does and supported first-class ranges —
let range = start..finish
— it would seem to be all the more desirable to deprecate { start..finish }
in favor of seq { start..finish }
to help differentiate the creation of a lazy sequence in something like let range = seq { start..finish }
from the creation of a System.Range
in let range = start..finish
.
[^1]: I would be interested in helping spec it out and/or implement it.
@brianrourkeboll i do agree that we should work towards type directed user defined collections before any other work on them.
Sorry, can't give extended comment right now, mostly reading those from phone, but great analysis and comments, thanks for that!
@brianrourkeboll Thanks, this is exactly the context I was looking for trying to understand what to do here 👍🏻. Looking at the long term plan I agree in that we should deprecate/discourage bare { start..finish } even we pass to collections.
i do agree that we should work towards type directed user defined collections before any other work on them.
@vzarytovskii is your comment suggesting to not implement this until https://github.com/fsharp/fslang-suggestions/issues/1086 is done ?
Some examples of existing code that would be affected by warning on { start..finish }
/{ start..step..finish }
without seq
(~1.1k public files via imperfect regex):
Versus ~3.5k public files with seq
:
https://github.com/search?q=%2Fseq\s*\{\s*-%3F\w%2B\s*\.\.%2F+language%3AF%23+&type=code
Redefining (..)
or (.. ..)
is fun:
let (..) _ _ = "lol"
let lol1 = { 1..10 } // val lol1: string = "lol"
let lol2 = seq { 1..10 } // val lol2: char seq = "lol"
let (..) _ _ = 99
let lol1 = { 1..10 } // val lol1: int = 99
let lol2 = seq { 1..10 } // error FS0001: The type 'int' is not compatible with the type ''a seq'
I think that's all the more reason to deprecate bare { start..finish }
/{ start..step..finish }
, since that is not the behavior I would have expected.
{ … }
is effectively just acting as a syntactic environment in which calling (..)
and (.. ..)
as infix operators is allowed — probably by accident^1.
{ start..finish }
and { start..step..finish }
only happen to behave the same as seq { start..finish }
and seq { start..step..finish }
in the common case because the (..)
and (.. ..)
operators defined in FSharp.Core return _ seq
.
> This case snuck through in F# 2.0 and should really have always requried a `seq`.
@vzarytovskii is your comment suggesting to not implement this until #1086 is done ?
In ideal world - yes. But the feature is very big, I think it's fine to meanwhile work on things like this (allow omitting seq when passed to collections constructors).
@vzarytovskii
allow omitting seq when passed to collections constructors
Hmm. I wasn't sure whether Don's comment https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088 really implied that that should be enabled (I guess we really need clarification from him), but that would actually be problematic now with https://github.com/dotnet/fsharp/pull/17387 / #632 in place:
type Class () = class end
let objExpr = { new Class () } // Class
let seqExpr = seq { new Class () } // Class seq
Hmm. I wasn't sure whether Don's comment #1033 (comment) really implied that that should be enabled
I think it was:
However I do like that set and other future collection construction operations can omit the seq without making those full computation expressions. That is, if the seq is being immediately consumed eagerly into a collection construction then I'm happy to omit it. However we have no specific way of telling if that's the case
Hmm. I wasn't sure whether Don's comment #1033 (comment) really implied that that should be enabled
I think it was:
However I do like that set and other future collection construction operations can omit the seq without making those full computation expressions. That is, if the seq is being immediately consumed eagerly into a collection construction then I'm happy to omit it. However we have no specific way of telling if that's the case
I guess it depends exactly what this means:
immediately consumed eagerly into a collection construction
Does that mean "passed into a collection type's constructor," like System.Collections.Generic.List<'T>
)) or FSharp.Core.Collections.Set<'T> ()
? Or any function that takes a seq
and returns another collection type? Sure, there are well-known ones like set
, but what about let mySetFunc (xs : _ seq) : Set<_> = ignore xs; Set.empty
? Should that allow mySetFunc { 1; 2 }
?
I think this part of Don's comment is important:
However we have no specific way of telling if that's the case
There's no way for the compiler to know whether a given constructor or function "immediately consumes the sequence eagerly," or whether the return type of a given constructor or function constitutes an eager collection type.
Hmm. I wasn't sure whether Don's comment #1033 (comment) really implied that that should be enabled
I think it was:
However I do like that set and other future collection construction operations can omit the seq without making those full computation expressions. That is, if the seq is being immediately consumed eagerly into a collection construction then I'm happy to omit it. However we have no specific way of telling if that's the case
I guess it depends exactly what this means:
immediately consumed eagerly into a collection construction
Does that mean "passed into a collection type's constructor," like
System.Collections.Generic.List<'T>
)) orFSharp.Core.Collections.Set<'T> ()
? Or any function that takes aseq
and returns another collection type? Sure, there are well-known ones likeset
, but what aboutlet mySetFunc (xs : _ seq) : Set<_> = ignore xs; Set.empty
? Should that allowmySetFunc { 1; 2 }
?
I think only collection "constructors" - set, dict, list, array.
I think this part of Don's comment is important:
However we have no specific way of telling if that's the case
There's no way for the compiler to know whether a given constructor or function "immediately consumes the sequence eagerly," or whether the return type of a given constructor or function constitutes an eager collection type.
Yes, that's why if we want to do it now, it will have to be special cased. Which brings us back to the custom user defined collections syntax.
I personally think we should not make an special case for collection "constructors"
bare { start..finish }
should be disallowed as it { start..finish }
. at the very least it will consistent across the language.
@dsyme Could you please clarify if this is still applicable ?
However I do like that set and other future collection construction operations can omit the seq without making those full computation expressions. That is, if the seq is being immediately consumed eagerly into a collection construction then I'm happy to omit it. However we have no specific way of telling if that's the case
As it would actually be problematic now with https://github.com/dotnet/fsharp/pull/17387 / https://github.com/fsharp/fslang-suggestions/issues/632
type Class () = class end
let objExpr = { new Class () } // Class
let seqExpr = seq { new Class () } // Class seq
Current implementation WIP
@dsyme Could you please clarify if this is still applicable?
It's not really an applicable comment, even back then - I guess I was saying "I could imagine places where { 1 .. 2 }
make sense, but that thing is not achievable in practice"
So I think the comment can be largely ignored.
It looks like consensus is to go ahead with requiring seq
I propose we make this consistent:
Now we don't have to allocate entire lists or arrays just to make sets.
The existing way of approaching this problem in F# is
Pros and Cons
The advantages of making this adjustment to F# are
The disadvantage of making this adjustment to F# is that this may cause confusion as
set
here isn't a computation expression. However, syntax can be quickly learnt.Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: (put links to related suggestions here)
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.