fsharp / fslang-suggestions

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

Indexer, collection, event and sublevel initialization #969

Open Happypig375 opened 3 years ago

Happypig375 commented 3 years ago

I propose we allow indexers, collections, and events to be assigned during construction. Currently, only properties and fields are allowed. An indexer is a property too, why can't we assign to it during construction?

As an extension to https://github.com/fsharp/fslang-suggestions/issues/877, I propose we allow:

let p = Builder([2] = initializeToIndexer, collection @= [addToCollection1; addToCollection2], event @= [addToEvent1; addToEvent2], Property = (SubLevelProp1 = 2, SubLevelProp2 = 1))

The existing way of approaching this problem in F# is

let p = Builder()
p.[2] <- initializeToIndexer
p.collection.Add addToCollection1
p.collection.Add addToCollection2
p.event.Add addToEvent1
p.event.Add addToEvent2
p.Property.SubLevelProp1 <- 2
p.Property.SubLevelProp2 <- 1

Pros and Cons

The advantages of making this adjustment to F# are

  1. Conciseness
  2. Convenience - property initialization works with more things!

The disadvantages of making this adjustment to F# are that @= may have already been defined in user code. However, as with nameof, this can be solved by a library intrinsic.

Extra information

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

Related suggestions: https://github.com/fsharp/fslang-suggestions/issues/877

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.

dsyme commented 3 years ago

I agree with this in principle, though I'm not convinced by the @= syntax - it may be possible to simply use =

I will mark this as approved in principle and we can sort out the details in prototype and RFC

Happypig375 commented 3 years ago

We need to differentiate between assignment and calling Add without assigning because it is assumed that an instance has been given by the constructor.

dsyme commented 3 years ago

We need to differentiate between assignment and calling Add without assigning because it is assumed that an instance has been given by the constructor.

Yes, if the collection property is settable this is true. The @= syntax does not feel intuitve however. Will think it over.

Events are not settable so would not need @=

charlesroddie commented 3 years ago

Of the four suggestsions here, I would use collection (and it's seen a lot in C# code for good reason) but not the others.

Happypig375 commented 3 years ago

Maybe we can even have a syntax that is equivalent to C#'s collection initialiser, such as ResizeArray<int>([] = [1; 2; 3])

For collection in a property: Builder(Collection[] = [1; 2; 3])

xp44mm commented 3 years ago

this issue is very similar to vb's with statement:

With MyLabel
    .Height = 2000
    .Width = 2000
    .Caption = "This is MyLabel"
End With
Happypig375 commented 3 years ago

@xp44mm No. This issue is a direct counterpart of VB (and C#)'s object initializers:

Dim MyLabel = New Label With {.Height = 2000, .Width = 2000, .Caption = "This is MyLabel"}
var MyLabel = new Label { Height = 2000, Width = 2000, Caption = "This is MyLabel" }

and collection initializers:

Dim labels = New List(Of Label) From { New Label(), New Label(), New Label() }

var labels = new List<Label> { new Label(), new Label(), new Label() }
xp44mm commented 3 years ago

Giving a prefix such as a dot . on a component gives the IDE the ability to prompt for component names.

let p = Builder(
    .[2] = initializeToIndexer, 
    .collection @= [addToCollection1; addToCollection2], 
    .event @= [addToEvent1; addToEvent2], 
    .Property = (.SubLevelProp1 = 2, .SubLevelProp2 = 1)
)
jl0pd commented 2 years ago

Just for the record it's possible to use collection, indexer and event initalizers even now with extension properties:

open System.Collections.Generic

type Dictionary<'key, 'value> with
    member inline dict.With with set keysAndValues =
        for k, v in keysAndValues do
            dict[k] <- v

new Dictionary<string, string>(
    With = [
        "key", "value"
        "key2", "value2"
    ]
) |> printfn "dict: %A"

open System.Collections.ObjectModel
open System.Collections.Specialized

type ObservableCollection<'a> with
    member inline col.OnCollectionChanged with set handlers =
        for handler in handlers do
            col.CollectionChanged.AddHandler handler

    member inline col.Items with set items =
        for item in items do
            col.Add item

new ObservableCollection<int>(
    OnCollectionChanged = [
        NotifyCollectionChangedEventHandler(
            fun sender e -> printfn "Collection changed. Action: %A; NewItems: %A; OldItems: %A" e.Action e.NewItems e.OldItems
        )
    ],
    Items = [ 1; 2; 3 ]
) |> printfn "observableCollection: %A"

Which works as people may expect, but have overhead of creating list and requires each property to have extension counterparts. I'm still waiting for direct language support and recommend to avoid this solution

wallymathieu commented 2 years ago

Re @dsyme

Yes, if the collection property is settable this is true. The @= syntax does not feel intuitve however. Will think it over.

Could you use the operators already available for consing?

dsyme commented 2 years ago

@Happypig375 Did you compare and contrast with https://github.com/fsharp/fslang-suggestions/issues/290? thanks

Happypig375 commented 2 years ago

For sublevel initialization, we can also allow the syntax of #290 for a single sublevel initialization. However, for multiple properties under one outer property it quickly becomes repetitive to have to specify the outer name over and over again. So we should allow the syntax of this issue. Meanwhile the collection initialization of #290 conflicts with directly setting the property which this issue avoids using new syntax.