Resolving overload ambiguity with null - f#

I'm working with an API in F# (that I have no control over) which declares two constructors like so:
public TheType(string bar, bool foo)
public TheType(IDisposable baz, bool foo)
I need to create an instance of the object using the first constructor, but passing null for bar. The F# compiler of course gets confused about which constructor I am trying to resolve:
//Private field
static let blah : TheType = new TheType(null, false)
I can resolve the issue by casting null to string:
static let blah : TheType = new TheType(null :> string, false)
Which works, but that gives me a compilation warning:
The type 'string' does not have any proper subtypes and need not be used as the target of a static coercion
Which also makes sense. Is there a way to resolve the overload ambiguity without resorting to compilation warnings?

You can specify the type of null:
static let blah : TheType = new TheType((null : string), false)

Related

Why does `if (var = null)` compile in dart?

I've recently came across this question How do I solve the 'Failed assertion: boolean expression must not be null' exception in Flutter
where the problem comes from a should be invalid code that gets treated as valid.
This code can be summarized as :
int stuff;
if (stuff = null) { // = instead of ==
}
But why does this code compiles ? As the following will not.
int stuff;
if (stuff = 42) {
}
With the following compile error :
Conditions must have a static type of 'bool'.
So I'd expect out of consistency that if (stuff = null) to gives the same error.
null is a valid value for a bool variable in Dart, at least until Dart supports non-nullable types.
bool foo = null;
or just
bool foo;
is valid.
Therefore in the first case there is nothing wrong from a statical analysis point of view.
In the 2nd case the type int is inferred because of the assignment, which is known to not be a valid boolean value.
bool foo = 42;
is invalid.
When you say var stuff; with no initial value it is giving stuff a static type of dynamic. Since dyamic might be a bool, it's legal to assign null to a variable of type dynamic, and it's legal to use a possibly null bool in a conditional, the compiler doesn't flag this. When you say int stuff; the compiler knows that stuff could not be a bool. The reported error in that case is cause by the static type of stuff, not the assignment to null.
Edit: Got the real answer from someone who knows how to read the spec.
The static type of an assignment expression is the right hand side of the assignment. So the expression stuff = null has the static type of Null which is assignable to bool.
The reasoning is that the value of an assignment is the right hand side, so it makes sense to also use it's type. This allows expressions like:
int foo;
num bar;
foo = bar = 1;
Commonly assignment operation returns the value that it assigns.
int a = 0;
print(a = 3);//Prints 3
So,
When stuff = null,
'stuff = null' returns null. if statement needs a boolean .null is a sub-Type of boolean.
if(null){}
is valid
When stuff = 42,
'stuff = 42' returns 42. if statement needs a boolean .42 is not a sub-Type of boolean.
if(42){}
is not valid

overload resolution of F# lambda vs Func

I'm adding a static builder method to a record type like this:
type ThingConfig = { url: string; token : string; } with
static member FromSettings (getSetting : (string -> string)) : ThingConfig =
{
url = getSetting "apiUrl";
token = getSetting "apiToken";
}
I can call it like this:
let config = ThingConfig.FromSettings mySettingsAccessor
Now the tricky part: I'd like to add a second overloaded builder for use from C# (ignore the duplicated implementation for now):
static member FromSettings (getSetting : System.Func<string,string>) : ThingConfig =
{
url = getSetting.Invoke "apiUrl";
token = getSetting.Invoke "apiToken";
}
This works for C#, but breaks my earlier F# call with
error FS0041: A unique overload for method 'FromSettings' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: static member ThingConfig.FromSettings : getSetting:(string -> string) -> ThingConfig, static member ThingConfig.FromSettings : getSetting:Func -> ThingConfig
Why can't F# figure out which one to call?
What would that type annotation look like? (Can I annotate the parameter type from the call site?)
Is there a better pattern for this kind of interop? (overloads accepting lambdas from both C# and F#)
Why can't F# figure out which one to call?
Overload resolution in F# is generally more limited than C#. The F# compiler will often, in the interest of safety, reject overloads that C# compiler sees as valid.
However, this specific case is a genuine ambiguity. In the interest of .NET interop, F# compiler has a special provision for lambda expressions: regularly, a lambda expression will be compiled to an F# function, but if the expected type is known to be Func<_,_>, the compiler will convert the lambda to a .NET delegate. This allows us to use .NET APIs built on higher-order functions, such as IEnumerable<_> (aka LINQ), without manually converting every single lambda.
So in your case, the compiler is genuinely confused: did you mean to keep the lambda expression as an F# function and call your F# overload, or did you mean to convert it to Func<_,_> and call the C# overload?
What would the type annotation look like?
To help the compiler out, you can explicitly state the type of the lambda expression to be string -> string, like so:
let cfg = ThingConfig.FromSettings( (fun s -> foo) : string -> string )
A slightly nicer approach would be to define the function outside of the FromSettings call:
let getSetting s = foo
let cfg = ThingConfig.FromSettings( getSetting )
This works fine, because automatic conversion to Func<_,_> only applies to lambda expressions written inline. The compiler will not convert just any function to a .NET delegate. Therefore, declaring getSetting outside of the FromSettings call makes its type unambiguously string -> string, and the overload resolution works.
EDIT: it turns out that the above no longer actually works. The current F# compiler will convert any function to a .NET delegate automatically, so even specifying the type as string -> string doesn't remove the ambiguity. Read on for other options.
Speaking of type annotations - you can choose the other overload in a similar way:
let cfg = ThingConfig.FromSettings( (fun s -> foo) : Func<_,_> )
Or using the Func constructor:
let cfg = ThingConfig.FromSettings( Func<_,_>(fun s -> foo) )
In both cases, the compiler knows that the type of the parameter is Func<_,_>, and so can choose the overload.
Is there a better pattern?
Overloads are generally bad. They, to some extent, obscure what is happening, making for programs that are harder to debug. I've lost count of bugs where C# overload resolution was picking IEnumerable instead of IQueryable, thus pulling the whole database to the .NET side.
What I usually do in these cases, I declare two methods with different names, then use CompiledNameAttribute to give them alternative names when viewed from C#. For example:
type ThingConfig = ...
[<CompiledName "FromSettingsFSharp">]
static member FromSettings (getSetting : (string -> string)) = ...
[<CompiledName "FromSettings">]
static member FromSettingsCSharp (getSetting : Func<string, string>) = ...
This way, the F# code will see two methods, FromSettings and FromSettingsCSharp, while C# code will see the same two methods, but named FromSettingsFSharp and FromSettings respectively. The intellisense experience will be a bit ugly (yet easily understandable!), but the finished code will look exactly the same in both languages.
Easier alternative: idiomatic naming
In F#, it is idiomatic to name functions with first character in the lower case. See the standard library for examples - Seq.empty, String.concat, etc. So what I would actually do in your situation, I would create two methods, one for F# named fromSettings, the other for C# named FromSettings:
type ThingConfig = ...
static member fromSettings (getSetting : string -> string) =
...
static member FromSettings (getSetting : Func<string,string>) =
ThingConfig.fromSettings getSetting.Invoke
(note also that the second method can be implemented in terms of the first one; you don't have to copy&paste the implementation)
Overload resolution is buggy in F#.
I filed already some cases, like this where it is obviously contradicting the spec.
As a workaround you can define the C# overload as an extension method:
module A =
type ThingConfig = { url: string; token : string; } with
static member FromSettings (getSetting : (string -> string)) : ThingConfig =
printfn "F#ish"
{
url = getSetting "apiUrl";
token = getSetting "apiToken";
}
module B =
open A
type ThingConfig with
static member FromSettings (getSetting : System.Func<string,string>) : ThingConfig =
printfn "C#ish"
{
url = getSetting.Invoke "apiUrl";
token = getSetting.Invoke "apiToken";
}
open A
open B
let mySettingsAccessor = fun (x:string) -> x
let mySettingsAccessorAsFunc = System.Func<_,_> (fun (x:string) -> x)
let configA = ThingConfig.FromSettings mySettingsAccessor // prints F#ish
let configB = ThingConfig.FromSettings mySettingsAccessorAsFunc // prints C#ish

Is it possible to use explicit member constraints on static fields?

Using F#, I had the following (simplified), which works fine:
type MyInt =
struct
val Value: int
new v = { Value = v }
end
static member inline name() = "my_int" // relevant line
let inline getName (tp: ^a): string = (^a: (static member name : unit -> string) ())
It seems to me that the statically resolved member signature from the explicit member constraint requires a function. I was wondering if it can also be used with a field instead. I tried a few things:
The following will compile, but won't work and will fail with the error
error FS0001: The type 'MyInt' does not support the operator 'get_name'
type MyInt =
//...
static member inline name = "my_int"
let inline getName (tp: ^a): string = (^a: (static member name : string) ())
Removing the () to prevent it from trying to call the gettor is a syntax error. If I change it to actually implement the gettor, it works (but that's essentially the same as the original code).
type MyInt =
// ...
static member inline name with get() = "my_int"
let inline getName (tp: ^a): string = (^a: (static member name : string) ())
Is there a way to get, using explicit member constraints or similar means, the compiler to find the static field? Or is this simply a limitation of the syntax of constraints?
Update:
Surprisingly, it does work with instance fields, or as in this case, struct fields: using (^a: (member Value: 'b) ()), which will call the member Value.

Difference between explicit member fields and automatic properties in F#

In Unity3D we are able to make a field accessible inside the editor by marking it as public. This then allows assigning the field's variable in the GUI instead of hard-coding it. This C# code for example will show a "speed" field that can be manually edited during development. It will default to 10 if left unmodified:
public class Example : MonoBehaviour {
public float speed = 10.0F;
}
I tried doing this in F# with automatic properties:
type Example() =
inherit MonoBehaviour()
member val speed = 10.f with get,set
but this doesn't work. It does, however, work if I use explicit properties
[<DefaultValue>] val mutable speed : float32
but this has the drawback of not being able to specify a default value in the same expression.
Aren't explicit and automatic properties compiling down to the same thing, with the only difference being that explicit properties are always initialized to zero? And how can I declare the equivalent of the C# code in F#?
I think you are playing a little loosely with the terms "field" and "property".
The Unity editor doesn't bind properties automatically, and the first example you've provided is F#'s auto-properties. For the record, you couldn't bind the following C# in Unity editor pane either:
// does not bind in editor either
class Example : MonoBehavior {
public float speed { get; set; }
}
You have to use the code with [DefaultValue] and just initalize it in the constructor or alternatively have a let-bound private field that is tagged [SerializeField] and write your own property wrapper:
type Example () =
[<SerializeField>]
let mutable _speed = 10f
member this.speed
with get () = _speed
and set val = _speed <- val
I think you're confusing two different concepts: explicit fields and auto-properties. Under the hood, a property is more like a method than a field, although access/assignment are syntactically similar. The F# equivalent of your C# would be:
type Example() as this =
[<DefaultValue>] val mutable public speed: float32;
do this.speed <- 10.0f
Another way to implement this, which avoids the [<DefaultValue>] and imperative initialization, would be as follows (note the absence of default constructor on the first line):
type Example =
val mutable public speed : float32
new() = { speed = 10.0f }

How can I use Nullable abstract types in F#?

Here is what am I trying to do :
and [<AbstractClass>] Figure() =
let mutable name : string = ""
let mutable color : ColorType = WHITE
abstract member Name : string
member F.Color
with get() = color
and set v = color <- v
abstract member motion : Dashboard -> SPosition -> ColorType -> list<SPosition * CellDashboard * bool>;
override F.ToString() = name
and CellDashboard(addingFigure : Nullable<Figure>) as C =
let mutable attacked : bool = false;
let mutable cleaned : bool = false;
let mutable _CellState : Nullable<Figure> = Nullable<Figure>(addingFigure);
the trouble is :
Error 1 A generic construct requires that the type 'Figure' be non-abstract
why ? And how can I avoid this error ?
The Nullable<T> type can only be used for .NET value types. The error message essentially means that the compiler cannot check whether the constraint holds or not (because the type is abstract).
If you want to represent missing value in F#, it is better to use option<T> instead.
If you're writing code that will be used from C#, then it is better to avoid exposing F#-specific types (like option<T>) and you can mark the type with AllowNullLiteral, which makes it possible to pass null as a value of the type (but null is evil, so this shouldn't be done unless necessary!)

Resources