fsharp / fslang-suggestions

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

Type-directed constructor resolution #1349

Closed Lanayx closed 9 months ago

Lanayx commented 9 months ago

I propose we add type-directed resolutions for calling constructors for known types.

type SomeVeryLongTypeA(x: string) =
   member this.X = x

type B() =
   member val A = Unchecked.defaultof<SomeVeryLongTypeA> with get, set

// old syntax
let x = B(A = SomeVeryLongTypeA("abc"))
// new syntax
let x = B(A = new("abc"))

Real world example:

let x = MyObj(
   To = Party44Choice(
          FiId = BranchAndFinancialInstitutionIdentification6(
              FinInstnId = FinancialInstitutionIdentification18(
                  Bicfi = "smth"
              )
          )
    )
)

would become

let x = MyObj(
    To = new (
          FiId = new (
              FinInstnId = new (
                  Bicfi = "smth"
              )
          )
     )
)

MyObj type is used for XML serialization so inner type names are not important, only properties are used

This is especially useful when using C# apis from autogenerated classes with long names where property name often corresponds it's type name. This also corresponds to C# feature and several other F# suggestions for type-directed collections, tuples and dictionaries.

The existing way of approaching this problem in F# is just to use full type names

Pros and Cons

The advantages of making this adjustment to F# are : enabling convenient using of C#-centrific API, also knowing that such APIs will be convenient to use from F#, improving conciseness, parity with other type-directed features

The disadvantages of making this adjustment to F# are : more type-inference work for compiler, less explicit code

Extra information

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

Related suggestions: https://github.com/fsharp/fslang-suggestions/issues/1086, https://github.com/fsharp/fslang-suggestions/issues/49, https://github.com/fsharp/fslang-suggestions/issues/988

Affidavit (please submit!)

Please tick these items 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.

vzarytovskii commented 9 months ago

Likely covered by https://github.com/fsharp/fslang-suggestions/issues/969

vzarytovskii commented 9 months ago

This won't make it more readable for sure, code will quickly be polluted with

Obj.SomeMethod(new(), new(42), new("foo"))

Definitely doesn't look great.

Lanayx commented 9 months ago

As with many other features, it's useful specifically in some scenarios. This feature is useful in scenarios where you operate a bunch of DTO classes that don't have anything but empty constructor and bunch of properties, so there is no difference in readability between proposed syntax and regular records creation: compare

let a = new ( X = 1, Y = 2 )
let b = { X = 1; Y = 2 } 
jl0pd commented 9 months ago

If objects have non-null properties by default (they're initialized at construction time) code from example can be translated to following C#. It doesn't override properties, instead it updates existing values. Requires DTOs to be mutable

var x = new MyObj
{
    To =
    {
        FiId = 
        {
           FinInstnId = { BicFi = "smth" }
        }
    }
}

Mentioned #969 discovers how to do it in F#.


In my opinion C#'s target-typed-new was a mistake. Language wouldn't need them if language had better type inference (at least holes in constructors are supported new MyThing<_>(arg)) and local functions required less code to write: MyThing<int> NewInstance(int a, string b) => new MyThing<int>(a, b);. In F# fn newInstance a b = MyThing(a, b). Also lack of var support in fields/properties. With these features target-typed-new wouldn't be so promising and probably wouldn't be accepted

Lanayx commented 9 months ago

@jl0pd Thanks! didn't know that you can even skip new() in nested properties in С# at all, it makes the code really clean! However, I don't see #969 has anything type-directed, could you please link specific comment you mean?

UPDATE: I've figured out that without new() C# version won't work, it will throw NRE https://sharplab.io/#v2:D4AQTABAghDeEgMwIIwAYIA04HMCmALgNwDOhRAvhBQLABQ4EAQnAsjAJq7lnFW1169RgGE49CJNQA2ZhAAqeEgRQAKAJQQAvAD4IAOzwB3Day5bW2CwCIAHterUiEAPQuAcgCUAohKkgUWRZFZTBTXQNjU3hzSJNNeCsIOwd+ZzcAdQB7ACcAaxJ6ASkIeiA===

jl0pd commented 9 months ago

It doesn't throw if property is initialized. Test1 will configure existing A, Test2 will reassign property with new instance A. https://sharplab.io/#v2:C4LgTgrgdgPgAgJgIwFgBQ7EAICCWDeWcAzEUgAxYAaBWA5gKbADcWAzk6wL5YC8WAIgHdMCLACFaJXFgCatRi3acsPflAYB3XAAoAlCIxHsAYQLosloqThIAbBKwAVBm2BJ9fAHxYNmz4Ty/IQ0/AIAHgKqqqwA9LEAcgBKAKIWVtK2DpIubgievD5+AXJ8vloloYKR0VxxsQDqAPZgANZs6FzpluiY9kRitgDs6PjdAwjjY2hWs2QAnDomAHS57vrLsstUBuOztosra/l6m9u7M1ZdaNdAA===

Happypig375 commented 9 months ago

The correct issue that this issue duplicates is not #969 but just one away:

vzarytovskii commented 9 months ago

Duplicate of https://github.com/fsharp/fslang-suggestions/issues/970

vzarytovskii commented 9 months ago

The correct issue that this issue duplicates is not #969 but just one away:

True, thanks. I closed it as dupe, however example in the OP might be solved it the one I linked, I think.