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

Deprecate places where seq can be omitted e.g. { 1.. 5 } #1033

Open
5 tasks done
Happypig375 opened this issue Jun 15, 2021 · 18 comments
Open
5 tasks done

Deprecate places where seq can be omitted e.g. { 1.. 5 } #1033

Happypig375 opened this issue Jun 15, 2021 · 18 comments

Comments

@Happypig375
Copy link
Contributor

Happypig375 commented Jun 15, 2021

I propose we make this consistent:

let f = Seq.head
let a = f { 1..6 }
let b = f { 1; 2 } // error FS0740: Invalid record, sequence or computation expression. Sequence expressions should be of the form 'seq { ... }'

Now we don't have to allocate entire lists or arrays just to make sets.

let a = set { 1..6 }
let b = set { 1; 2 } // error FS0740: Invalid record, sequence or computation expression. Sequence expressions should be of the form 'seq { ... }'

The existing way of approaching this problem in F# is

let a = set { 1..6 }
let b = set (seq { 1; 2 })

Pros and Cons

The advantages of making this adjustment to F# are

  1. Consistency
  2. Convenience
  3. Less allocation

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:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

@charlesroddie
Copy link

Too much of a special case. If seq { 1..6 } requires the seq then set(seq { 1..6 }) should require the seq.

@TheJayMann
Copy link

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)).

@Happypig375
Copy link
Contributor Author

Happypig375 commented Jun 15, 2021

Oh, { 1 .. 6 } produces a range. That is interesting actually.

@cartermp
Copy link
Member

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.

@dsyme
Copy link
Collaborator

dsyme commented Jun 15, 2021

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

  1. { x .. y }
  2. { 1; 2 }
  3. { if true then yield 1 } and other computations

Only (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

  1. the laziness is made obvious
  2. it makes things distinct from object expressions and record expressions
  3. it introduces people to computation expressions.

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.

@dsyme dsyme changed the title Implicit seq construction when curly braces are provided to seq arguments Deprecate places where seq can be omitted e.g. { 1.. 5 } Jun 16, 2022
@dsyme
Copy link
Collaborator

dsyme commented Apr 13, 2023

Approving the variation described here: #1033 (comment)

@brianrourkeboll
Copy link

brianrourkeboll commented Sep 21, 2024

@edgarfgp & @vzarytovskii regarding dotnet/fsharp#17772 and #1033 (comment):

TL;DR

It seems like #1086 (which, incidentally, Don opened after #1033 (comment)) would obviate the need for enabling set { x; y } specifically, and I'd personally rather work towards1 some version of #1086 in the long run.

My understanding of #1033 (comment) is that, if anything, we should deprecate/discourage bare { start..finish }.

Long answer

This part of #1033 (comment)

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.

— seems like it would be effectively subsumed by #1086 instead.

#1086 would enable

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 a seq.

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.

Side note

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 #1086 (comment):

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.

Edit: Addendum

We should also consider what directions we might take with #1044 and #1116 (see also #1317 (comment) and #1317 (comment)).

For example, depending on how far we went in treating start..finish as System.Range (#1044 (comment)), 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:

SharpLab

let f start finish = for x in start..finish do ignore (float x ** float x) 

SharpLab

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.

Footnotes

  1. I would be interested in helping spec it out and/or implement it.

@vzarytovskii
Copy link

@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!

@edgarfgp
Copy link

edgarfgp commented Sep 21, 2024

@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 #1086 is done ?

@brianrourkeboll
Copy link

brianrourkeboll commented Sep 21, 2024

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):

https://github.com/search?q=%2F%5B%5E%5Cw%5Cs%5D%2B%5Cs*%5C%7B%5Cs*-%3F%5Cw%2B%5Cs*%5C.%5C.%2F+language%3AF%23+&type=code

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

@brianrourkeboll
Copy link

brianrourkeboll commented Sep 21, 2024

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 accident1.

{ 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.

Footnotes

  1. https://github.com/fsharp/fslang-suggestions/issues/1033#issuecomment-861688088

    This case snuck through in F# 2.0 and should really have always requried a seq.

@vzarytovskii
Copy link

@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).

@brianrourkeboll
Copy link

brianrourkeboll commented Sep 22, 2024

@vzarytovskii

allow omitting seq when passed to collections constructors

Hmm. I wasn't sure whether Don's comment #1033 (comment) really implied that that should be enabled (I guess we really need clarification from him), but that would actually be problematic now with dotnet/fsharp#17387 / #632 in place:

type Class () = class end

let objExpr = { new Class () }     // Class
let seqExpr = seq { new Class () } // Class seq

@vzarytovskii
Copy link

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

@brianrourkeboll
Copy link

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.

@vzarytovskii
Copy link

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 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.

@edgarfgp
Copy link

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 dotnet/fsharp#17387 / #632

type Class () = class end

let objExpr = { new Class () }     // Class
let seqExpr = seq { new Class () } // Class seq

Current implementation WIP

@dsyme
Copy link
Collaborator

dsyme commented Sep 25, 2024

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants