-
Notifications
You must be signed in to change notification settings - Fork 22
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
Allow defining custom literal syntax for creating collections #625
Comments
A few comments:
|
For reference, when designing the Fable.React bindings I chose to use list because it's much more idiomatic in F#, Saying this, I acknowledge this change could harm readability. I've already found the |
How about adapting the computation expression builder mechanism (and syntax) to be used as programmable collection literals? This is what
Would it be possible to either extend the existing builder mechanism to implicitly assume yields in each line (under some reasonable conditions), or otherwise introduce a new kind of builder with a set of methods strictly targeting collection construction? So that we can write something like this:
and then perhaps make an "unqualified"
The benefits of this approach:
|
When talking about |
When defining
This distinction comes up in cases like the PersistentVector data type, where adding an element at the end of the vector is O(1), but adding it at the start of the vector is O(N). Likewise, the
Again, with the PersistentVector data structure, it's O(1) to remove the last element, but O(N) to remove the first element. So an implementation of PersistentVector would prefer to write an Or there's a third approach possible, which would be to say that
For collections that do not maintain order, then the The advantages and disadvantages of these three approaches would be:
The question of semantics for |
Sorry, I just realized that my comment above was not entirely correct. Actually the situation is a bit more complicated, because in Fable.React bindings the first list (attributes) compiles to a JS object and the second compiles to spread arguments. So something like: div [
Width 60.
Height 40.
] [str "foo"; str "bar"] In JS becomes: React.createElement("div", {width: 60, height: 40}, "foo", "bar") When possible this transformations are done in compile time, but in other cases this happens at runtime. There's some performance penalty but using arrays probably wouldn't improve the situation drastically. In conclusion, a change like this wouldn't necessarily benefit Fable.React bindings. |
If this suggestion were implemented, I think this part would not require any new type annotation syntax - we could simply provide a trivial function (constructor or not)
and let type inference do the rest.
I think it's exactly the same issue as |
@piaste - But in Yes, there are collections (such as linked lists) where it matters which side is the large side. But since you can't know ahead of time which will be which, the best you can do is document that |
I like this one if possible |
@rmunn For your third approach, what about the associativity?
I'm no expert, but I doubt that it is possible for one operator to be either right or left associative depending on the types of the arguments, as it would require the typechecking to happen before the parsing? You could use right associativity for both cases even though it isn't ideal - there is precedent for incorrect associativity with the |
If we're considering altering the type of If let l = [1; 2] and an array here: let a: int[] = [1; 2] Then there is a symmetry in let i = 12 and a byte here: let b: byte = 12 |
It seems like we're talking some pretty major changes here. What would be the impact on backwards compatibility? |
As @dsyme pointed out, this might not be a breaking change if the default type for the But there would certainly be an impact on documentation. Pretty much everything that's been written about F# lists would have to be revised to say that the And having syntax that changes based on what context it's used in is one of the biggest negatives of this proposal. Perl's "scalar context" vs. "list context", and how the context can change the nature of what's passed in, causes LOTS of confusion (and thus, bugs) in Perl code. The story is better for F# than for Perl because the compiler would catch most errors of this type since they'd be type errors. (Perl is dynamically typed, so the compiler won't catch those errors for you in Perl). But the biggest danger arising from this proposal, as I see it, isn't backwards-incompatibility, it's the potential for confusion. Any time you see the |
I really like this proposal. Thinking about possible breaks / bugs I would vote to disallow using [ ] for seq. The reason is eager vs lazy, and possible multiple-enumeration. Consider: // dummy, pretend this is actually expensive.
let getDataFromDatabase() = [1;2;3]
let data = [
let d = getDataFromDatabase()
printfn "Got data from db"
for x in d ->
printfn "Got %A" x
x
]
data
|> Seq.iter (fun x -> ())
data
|> Seq.iter (fun x -> ()) with this proposal, and depending on the actual implementation, it might infer data as |
I do agree that it'd make sense to keep this eager - it'd keep one more piece of complexity out of the equation. I would rather it "implement seq via list" vs "disallow seq". This would work fine since a list is a seq. Not all seq implementations are lazy evaluated - and there's nothing saying this one could/should be, as it's a new feature. You could potentially even inferring the type as list if it's ambiguous/could work either way, which would make That'd keep it more closely aligned to existing code (where it's always list), but not have a surprising type to an end user, as they'd expect a seq. |
I think "disallow" was the wrong term to use.
is basically what I meant. The code I posted compiles today, so it needs to also compile tomorrow. But it should stay list, not become seq. More concrete, I would disallow this: let doIter (s:seq _) = ...
doIter [ 1;2;3 ] (type error) and allow this but make sure this is list-typed, NOT seq-typed: let doIter (s:#seq _) = ...
doIter [ 1;2;3 ] |
That's where I disagree - I would still allow that. I would just make the |
My gut feeling is that in most cases F# values explicit over implicit. And having a binding typed as seq but actually internally be a list without any visible indicators feels wrong in that regard somehow. You can always cast it ([ 1;2;3 ] :> seq _). But I don't have too strong an opinion on this, my main goal was to give attention to lazy vs eager and multiple-enumeration. |
@0x53A There is no "seq" type - a seq always has to be implemented by some type under the hood. What disadvantage would using list have over any other type? It's really just an implementation detail at that point. |
If you use the let s1 = seq { printfn "hello"; yield 1 } // lazy, can print hello multiple times if iterated multiple times (or never, if not iterated)
let s2 : int seq = [ printfn "hello"; yield 1 ] // ???
let a : int array = [ printfn "hello"; yield 1 ] // eager, prints hello once
let l : int list = [ printfn "hello"; yield 1 ] // eager, prints hello once If |
It's true that |
I don't think that's the case. |
Yes, sorry. What I wanted to say is iterating a lazy |
This is a pretty neat idea. My only concern is that we might see breakage for existing places in the code where type inference depends on these. How would you add support for this to new data structures? Would it only be able to apply to things in F# Core? |
Let's introduce an interface or duck typing so that |
@dsyme is there any considerations for F# 5 about this? |
As a new major version of F# comes, we could possibly switch to System.Collections.Immutable as primary collections but for backward compatibility move old collections out to a separate package. |
@xperiandri |
There's relevant discussion going on here: fsharp/fslang-design#528 (comment) |
C# unifying collection literal syntax - dotnet/csharplang#5354 |
I propose we allow custom literal syntax to be defined for creating user-defined collections. E.g., the syntax
[ ... ]
, which currently is reserved for creating lists, could be declared to build an array, or a mathematical vector, or a PersistentVector from FSharpx.Collections, as long as the appropriate type (and how to construct the literal) can be determined at compile-time.The existing way of approaching this problem in F# is to use
[| ... |]
syntax for arrays, and to use something likeVector.ofList [ 1; 2; 3 ]
for other types, which tends to be uglier and/or harder to type than plain[ 1; 2; 3 ]
.Rationale
In #619 (comment), @piaste made an interesting observation:
This triggered a discussion which let to @dsyme suggesting the following as a possible approach:
Many people seemed to like the idea, so I've created this issue to track that particular suggestion.
Pros and Cons
The advantages of making this adjustment to F# are that many user-defined data types will appear a lot "cleaner" to use. For example, if someone adds a Relaxed Radix-Balanced vector type to FSharpx.Collections, creating one would be as simple as:
Then if the primary constructor for RRBVector takes an array, then the
[ 1; 2; 3 ]
syntax would be turned into an array at compile-time and then passed to the RRBVector constructor at runtime. (In many cases, the type ofvec
would be determinable by type inference and it wouldn't be necessary to specify its type explicitly like this).The disadvantages of making this adjustment to F# are twofold: first, there would be a significant amount of effort involved. Second, the
[ ... ]
syntax would become more "generic" and more cases like value restriction could be created. E.g.,let coll = []
would now not only need to determine the type of the contents ofcoll
, but also the type of the collection itself, before that code could compile.This would be a breaking change to F#.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): L
Related suggestions: #49 is a subset of this idea, and #619 is the discussion that led to this idea.
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: