fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
345 stars 21 forks source link

A normative immutable array type #619

Open dsyme opened 6 years ago

dsyme commented 6 years ago

For people coming to this thread afresh, the RFC is here: https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1094-immarray.md

I propose we work out how to make one particular immutable array type normative in F# programming "in the box", including in Fable programming.

The existing way of approaching this problem in F# is to use a user-supplied package of collections such System.Collections.Immutable

Description

One particular thing that is a hole in our library is the lack of an immutable array data structure in regular F# coding. There are lots of use cases for this and it is easy enough to implement efficiently, e.g. here (originally from the compiler codebase) though there are other approaches.

I’m particularly aware that Fable and the Elmish design pattern is popularizing the use of immutable data for important model descriptions more and more and we should be helping improve the situation for that kind of programming

The main question is to how to make on immutable array type normative in F# coding

  1. Add a bespoke immutable array to FSharp.Core.

  2. Encourage people to take a dependency on System.Collections.Immutable and add a reference to it to our standard templates. However we would still presumably want an FSharp.Core module making it look and feel like a normal F# collection, but we wouldn't want FSharp.Core to have a dependency on System.Collections.Immutable.

Probably the hardest thing is to decide its name.

Related questions are

  1. Would want a bespoke functional update syntax “{ arr with 3 = expr }” or “{ arr with n = expr } or “arr.[n=expr]”.
  2. Would the type feel right from F# code – good ergonomics etc
  3. What library dependencies would the type induce
  4. How do other collections from System.Collections.Immutable feel to use from F#?

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Affidavit (please submit!)

Please tick this by placing a cross in the box:

Please tick all that apply:

TIHan commented 4 years ago

Another comment:

varon commented 4 years ago

Just to emphasise; getting this done and available soon is way more important than whatever syntax we end up with or whatever we decide to call it.

Using mutable arrays sucks, but gets done anyway because for some tasks everything else is just too hopelessly slow.

Happypig375 commented 4 years ago

Just to emphasise; getting this done and available soon is way more important than whatever syntax we end up with or whatever we decide to call it.

Which is exactly the pain point of C++ - feature bloat and lack of cohesiveness.

varon commented 4 years ago

@Happypig375 - and yet right now we're forced to use incorrect code or even MORE incoherent and ugly syntax via third party libraries.

Get something on the table - if we weren't so terrified of breaking changes we can always take steps to improve/alter it later.

Happypig375 commented 4 years ago

and yet right now we're forced to use incorrect code or even MORE incoherent and ugly syntax via third party libraries.

That can be said so for almost all of the suggestions in this repository.

Get something on the table - if we weren't so terrified of breaking changes we can always take steps to improve/alter it later.

Python 2/3, VB6/VB.NET, Perl5/Perl6, ...

abelbraaksma commented 4 years ago

if we weren't so terrified of breaking changes we can always take steps to improve/alter it later.

@varon Nobody is terrified it's just a terrible idea. Breaking things stops adoption, loses customers and creates distrust in the future of a product. It's the cornerstone of good design to keep things compatible. Personally, I'm very happy with the design of F#. It's clean and concise, and for the majority of tasks, stopping yourself from writing foo.[3] <- x goes a long way.

I agree that using immutable libraries is a pain, but it's not hard and doesn't introduce real problems. Dedicated syntax would be nice-to-have, but if you've followed the discussion you know it's not that simple. Luckily, we have a feature switch that we can use to test-drive new implementations, while a feature isn't completed yet.

But before anything, we'll need concensus. Nobody likes to do the work, only to find out months later that there isn't concensus. But considering the discussion is active, I've good hopes we'll get agreement soon. Then we'll need a volunteer to do the heavy lifting, as my guess is this may be a rather tough task (depending on what features is decided upon).

Or maybe someone already has a PR or a fork ready so we could play around with this feature, that would be nice too. After all, it's open source ;).

brettfo commented 4 years ago

Late to the party, but is the syntax [::] available, e.g. [: 1; 2; 3 :], or does that collide or cause ambiguities with something else? I'm also partial to adopting int immarray as a shortcut for ImmutableArray<int>.

dsyme commented 4 years ago

@TIHan

We should definitely use the framework's ImmutableArray<'T> type, not provide our own.

Really? System.Collections.Immutable is not part of the default reference set for .NET Library (.NET Standard 2.0 or 2.1). This means every single F# project would need a reference to this assembly. Even in 2020 I'm somehow not happy with that.

Could you explain more on what you think the tradeoffs are here? Is S.C.Immutable now "part of the framework" enough that a 100% mandatory FSharp.COre dependency is desirable??

dsyme commented 4 years ago

Rules of discussion for this thread going forward

cartermp commented 4 years ago

S.C.I. is the de facto package in .NET for this sort of stuff and has undergone tremendous hardening and stability as a result of being use ubiquitously throughout the Roslyn and other massive codebases at Microsoft. I think we should very strongly consider incorporating it, or at least an F# implementation of a subset of it, into FSharp.Core for the purposes of having a standard immutable array type. I wouldn't really be supportive of a bespoke implementation.

What's the issue with adding the package dependency? The main issue I see isn't about including another dependency (it's 2020 - people have dependencies in their apps), but some potential confusion with things like F# lists vs. ImmutableList<'T>, which would be available to them via transitive references.

xperiandri commented 4 years ago

No breaking change for language version 5?

charlesroddie commented 4 years ago

S.C.I. is in netcore3.1. So it will be in dotnet5. Presumably you won't see an extra dependency when using dotnet5. In any case the extra package will be a small and short-lived problem.

NinoFloris commented 4 years ago

I would also be strongly in favor of using S.C.I. We use these collections very frequently in our codebase (with some aliases in F# for brevity, which I would propose to include). Adding new collections just so they can be shipped in F# Core will hinder interop and only increases the already fair amount of concrete collection types commonly seen in .NET and F#.

piaste commented 4 years ago

Also F# already does something like that by using its own List type and aliasing the S.C.G. List as ResizeArray, instead of providing its own dynamic array implementation.

Of course, S.C.G. List is in the standard framework unlike S.C.I., so the infrastructural concern of an external dependency doesn't apply. But the UX is similar.

Liminiens commented 4 years ago

Are there any perfomance reasons for F# to have a new immutable array type? Like, can compiler do some optimizations on it when on ImmutableArray it couldn't?

abelbraaksma commented 4 years ago

Like, can compiler do some optimizations on it when on ImmutableArray it couldn't?

The first thing that comes to mind is that it can eliminate null checks, something I believe is already done now for lists and arrays (the null check is in Core, but gets eliminated when used with F# code).

Other things currently not done in ImmutableArray is optimizations when target size is known (i.e. for things like map). I can envision something like, but not the same as Array.zeroCreate for arrays (which, btw, is not really optimized currently, but that's another discussion), and then use mutability internally. (the current ImmutableArray uses a Builder, we should measure if there's enough performance gain over that that warrants a self build implementation).

Happypig375 commented 4 years ago

Then it will have to point to a ReadOnlyMemory or confusion will occur.

SamuelBerger commented 4 years ago

Arr comes to my mind.

let x = arr [1; 2] // looks similar to seq {1; 2;}
let y = x |> Arr.map string 

// type inference for collection literals 
// e.g. in Feliz Html type: static member button (children: ReactElement arr) = ...
Html.button [
    prop.text "Increment"
    prop.onClick (fun _ -> dispatch Increment)
]
Evangelink commented 3 years ago

I am also coming from C# and I wouldn't be confused at all to have array being immutable and introduce MutableArray similarly to list and ResizeArray. It actually makes sense to me.

I also like the suggestion to have all types being alias around the immutable collections IF it does bring too much performance penalty. I am converting an application I started in C# and most of my types are ImmutableXXX types and so far I am doing the change because the syntax is too cumbersome for F#.

Regarding the fact that it will force a dependency upon immutable collections library. I do see a lot more projects starting to have a dependency upon this. I would say that it would be worth asking if SCImmutable should not be part of the default fwk moving forward.

I don't know if this was implied by @SamuelBerger on his last comment but I think it would be nice if there was only the [] syntax with the array or list prefix to define the type. We could still fallback to the old-way by having [] being implicitly understood as list but I would still recommend a warning/message to encourage people to move to the new syntax. Also maybe the prefix could be optional if the type is known:

let x = array [ 1; 2 ] // prefix is mandatory as we don't know
Html.button [ ... ] // prefix is optional as we know a list is expected
SamuelBerger commented 3 years ago

I don't know if this was implied by @SamuelBerger on his last comment but I think it would be nice if there was only the [] syntax with the array or list prefix to define the type. We could still fallback to the old-way by having [] being implicitly understood as list but I would still recommend a warning/message to encourage people to move to the new syntax. Also maybe the prefix could be optional if the type is known:

I meant 2 different things, naming and type inference for collection literals.

For the naming part:

Probably the hardest thing is to decide its name.

ImmutableArray is too long, especially for a module IArray looks like an interface FlatList breaks with industry naming, it looks too odd. XArray, ZArray are possible if we are desperate – e.g. just make a new F# convention that ZThing is an immutable version of Thing

My day to day F# progamming is mostly around List.xxx, Seq.xxx, Array.xxx and ResizeArray.xxx (custom module) and unfortunately the conversion between them. List because of its immutability and beauty. Seq mostly when types come from the BCL or C#. Array for its performance (when I know a list may contain thousands of elements I just have a bad conscious using List and lean towards Array or Seq 😬). If ImmutableArray was built in, all my collection usage would essentially collapse to this single type. I can imagine, that would be the case for a lot of people. That's why I think the name should be very short and easy to type. I think Arr would be the perfect name.

This part would have a huge impact by its own.

For the second part I was just quoting the suggestion from above 'type inference for collection literals' by @piaste and @dsyme.

Are there any approaches to literal syntaxes would be acceptable? e.g.

  • Add a range of new syntaxes [! a; b !] ?
  • Have the [ ... ] syntax resolved in a type-directed way (e.g. "if the target type is known to be a collection C with a builder pattern or IEnumerable constructor, then treat the syntax as if it is constructing a C, otherwise treat it as a regular F# list"))? While plausible it is a significant addition which may give worse error messages, and increase reliance on typing context and type annotations.

[ ... ] would be a 'significant addition' but I'm afraid the only good solution for front-end programming to replace List with the native JS Array. It would be intersting to hear the opinons of @alfonsogarciacaro and @Zaid-Ajaj what a difference it would make in e.g. Feliz.

charlesroddie commented 3 years ago

I support @brettfo 's [: :] over arr [ ].

  1. People are used to [ ] and [| |] for list and array. ImmutableArray construction is more like list and array construction than a seq { } expression so the syntax should follow that.
  2. arr [ ] gives the inaccurate feeling of creating a list and then applying the function arr to it.
  3. [: :] is easier to format across multiple lines without feeling guilty about undentation.

There are other ideas in this thread about using a flexible [ ] for construction and/or matching and having the compiler understand that this syntax can apply to ImmutableArrays. This is a great idea and might presage some exciting changes in the language, where [ ] and Option and ( ) get interpreted as ImmutableArray and ValueOption and ValueTuple respectively based on a project setting, resolving the confict between the most often useful type vs. easiest type to read and write. But it's a large change and long-term discussion that shouldn't prevent putting in ImmutableArrays now using the conventions we currently have.

Flexible [ ] is also orthogonal to a [: :] syntax:

dsyme commented 3 years ago

Perhaps [: :] is ok although it really screams type annotation to me in F#. But I do get the point that something is needed for symmetry for such a critical addition to the F# armoury.

@charlesroddie Do you have opinions on the name of a standard F# abbreviation for ImmutableArray<T>?

@cartermp do you really think we could take the S.C.I dependency for FSharp.Core? If so then we should go ahead and do this once a name is chosen. People are promoting "Arr" but no choices are great.

Vote:

gsomix commented 3 years ago

@dsyme I'd prefer a little shorter imarray.

jwosty commented 3 years ago

@dsyme I'd prefer a little shorter imarray.

Me too. It's a little more fluid to type quickly. imarray would be my first choice, followed by the double-m immarray.

dsyme commented 3 years ago

@dsyme I'd prefer a little shorter imarray.

I've added that an an option (but are you proposing ImmArray, ImArray or Imarray for the module name??`

gsomix commented 3 years ago

@dsyme ImArray is consistent.

dsyme commented 3 years ago

Anyone know the name for this emojii?
image

gsomix commented 3 years ago

It's :confused:!

matthewcrews commented 3 years ago

Trying to figure out how to respond with 😏but GitHub isn't letting me. I prefer the ImArray

jwosty commented 3 years ago

@matthewcrews use the :confused: one

aaronpalmer commented 3 years ago

Have you considered using double brackets? [[ 1;2;3 ]] I think it looks extra strong, immutable.

steveofficer commented 3 years ago

Could it be thought of in a different way? Rather than an ImmutableArray could it be something like an indexable list? The F# list is already immutable so it would be a case of indicating that it can be randomly accessed, something like IndexedList or RandomAccessList?

That might yield some alternative ideas?

jwosty commented 3 years ago

@aaronpalmer that would make jagged lists (i.e. int list list) ambiguous, unfortunately

jschiefer commented 3 years ago

One thing I like about F# is that it makes you do a little extra work every time you want something mutable. I like "Arr" for that reason, because it is less friction than any of the others. It is the easiest to type, the first one in a sorted documentation index, etc.

lfr commented 3 years ago

I agree with @jschiefer, there's a tradition in F# to encourage good practice (such as immutability) through simpler code such that a dev often finds they're doing the right thing without even thinking about it. Only "Arr" seems to respect this tradition. Also IArray and similar suggestions seem to overstep the boundaries of interop where IStuff comes with a lot of baggage.

dsyme commented 3 years ago

@lfr It would be against F# tradition to have two names, Array and Arr, one an abbreviation of the other, where the beginner has nothing to help understand the difference

dsyme commented 3 years ago

Note there's also a suggestion of "Irray".

lfr commented 3 years ago

@lfr It would be against F# tradition to have two names, Array and Arr, one an abbreviation of the other, where the beginner has nothing to help understand the difference

I agree, I forgot to add to my comment that I'm not particularly fond of Arr 🙁

lfr commented 3 years ago

Note there's also a suggestion of "Irray".

I like the idea but the word makes me angry, not sure why, perhaps it makes me think of Ire... What about Krray?

IltaySaeedi commented 3 years ago

Will you add matrix or it is a part of ImmutableArray(ImmutableArray2D)?

dylanbeattie commented 3 years ago

What about Imray.map and int imray and [: 1;2;3 :]

It's not a prefix or abbreviation of an existing name. It's pronounceable, it's not based on prefixing the word "array", and doesn't risk the confusion of irray and array sounding similar.

Just a thought...

brettrowberry commented 3 years ago

If the literals used < >, would that make a DSL that looked just like XML/HTML possible? Perhaps generics using <> and attributes using [<>] makes this impossible?

object commented 3 years ago

I can't help myself but I read imarray as "I'm array". And I agree with @jschiefer so I voted for arr.

cartermp commented 3 years ago

I know it's a little wordy but I still kept my vote for "ImmArray" and would probably prefer "ImmutableArray" if it weren't for System.Collections.Immutable.ImmutableArray already being a thing.

davedawkins commented 3 years ago

Maybe too late for suggestions but I'd consider "arrayi" . Treat the immutable property as a qualifier rather than being integral to the name. I did scan the thread for this suggestion but might have missed it, on mobile.

realparadyne commented 3 years ago

What about Imray.map and int imray and [: 1;2;3 :]

This one just jumps out at me as 'right' The same length to type as array, looks sufficiently different to spot it when reading, even easy to say. Could you add this one to the list @dsyme ?

lfr commented 3 years ago

@realparadyne I agree, I also like that it's pronounceable

dsyme commented 3 years ago

What about Imray.map and int imray and [: 1;2;3 :]

This one just jumps out at me as 'right' The same length to type as array, looks sufficiently different to spot it when reading, even easy to say. Could you add this one to the list @dsyme ?

I'll run another poll with a smaller set of suggestions including this one

dsyme commented 3 years ago

Here's a new suggestion:

When I think of "block" it conveys both the shape (array-like) and the properties (can't be changed, it's a block, which sort of sounds like something immutable)

Also, I can see F# education material "chapter 2" laying out the differences between

Somehow the fact they each have different names appeals to me.

dsyme commented 3 years ago

Second round of voting with top three suggestions plus "Imray" and "Block". You can vote for more than one suggestion.

You can work on the assumption that the type name is an alias for System.Collections.Immutable.ImmutableArray.

Also please work on the assumption that this type occurs very, very often in F# coding and education material and APIs going forward, often used as a replacement for both lists and arrays.