fsprojects / FSharp.TypeProviders.SDK

The SDK for creating F# type providers
https://fsprojects.github.io/FSharp.TypeProviders.SDK/
MIT License
298 stars 94 forks source link

ProvidedParameter: make it optional without a default value #312

Closed ivelten closed 2 years ago

ivelten commented 5 years ago

Description

I am developing a provided type having properties that can be options. For this type, I want a constructor that takes all those properties as arguments, to fill them (more or less like the constructor of a record type). However, properties that are options are being mapped to optional arguments.

But those arguments are not Options, instead, they take the underlying option type as the type. For example a type Record with two properties, A : int and B: int option would have a constructor with this signature:

new(a: int, ?b: int)

The second parameter of the constructor would need to be mapped with optionalValue filled, but I can't assume a default value for int as zero. If I try passing None or null, and coerce it as an object inside the quotation, it gives me zero as a value:

// ...
let prm = ProvidedParameter("b", typeof<int>, optionalValue = null)
// ...
let invoker (args : Expr list) =
    let b = Expr.Coerce(args.[1], typeof<obj>) // This returns zero instead of null

Expected behavior

I expect to have an optional parameter of a value type and have the option to know if the user didn't pass a value, without having to explicitly receive a default value.

Actual behavior

If I want to provide an optional parameter, I must explicitly pass a default value, and that value needs to be the same type of the parameter, so value types can not have a valid representation of "empty" or a way to know "if the user didn't provide a value".

Known workarounds

1. Make optional parameters of Option types. That would be a design that I would like to avoid since if I don't want to pass a value, I should just not be passing a parameter, or if I want to pass a value, I should not pass it as Some value, but simply as value.

2. Make constructor overloads. This is the thing I am trying at the moment. However, that could become hard to manage if I have a provided type with a lot of optional properties.

Tarmil commented 5 years ago

The reason for this is that the TP generates C#-style optionals, ie the equivalent of this:

new(a: int, [<Optional; DefaultParameterValue 0>] b: int)

aka this in C#:

MyClass(int a, int b = 0)

and not F#-style optionals, ie the equivalent of this:

new(a: int, ?b: int)
ivelten commented 5 years ago

Yes, I figured it out, @Tarmil. I just ended an implementation of a combination function to generate overloads for optional parameters. It's working well, even with a lot of overloads. I guess this will not be an issue, since in theory, optional parameters seems to be compiled into method overloads at the IL.

dsyme commented 2 years ago

Closing this old issue