Warning FS1178 "does not support structural equality" - f#

I tried enabling warnings level 5 over a large codebase and it mostly triggered hundreds of these:
Warning FS1178 The struct, record or union type 'Foo' does not support
structural equality because the type '(unit -> IBar option)' does not
satisfy the 'equality' constraint. Consider adding the 'NoEquality'
attribute to the type 'Foo' to clarify that the type does not support
structural equality
Many of our types reference C# types, or contain function types (like the above). What would it gain us to go through all of these and the NoComparison/NoEquality attributes as required?

If you try to compare Foo instances elsewhere in your code, the presence of [<NoComparison>] or [<NoEquality>] on Foo will make the compiler errors generated by that subsequent code easier to understand.
Example without attribute:
type Foo =
{
Func : int -> int
}
let problem = Set<Foo>
(*
Compiler error:
The type 'Foo' does not support the 'comparison' constraint
because it is a record, union or struct with one or more
structural element types which do not support the
'comparison' constraint. Either avoid the use of comparison
with this type, or add the 'StructuralComparison' attribute
to the type to determine which field type does not support
comparison
*)
Example with attribute:
[<NoComparison>]
type Foo =
{
Func : int -> int
}
let problem = Set<Foo>
(*
Simpler compiler error:
The type 'Foo' does not support the 'comparison' constraint
because it has the 'NoComparison' attribute
*)

Related

Is there a way to correctly suppress an F# Type Constraint Warning?

I have a Type Constraint by design Explicitly defined for a set of functions that I intend to use Types with a corresponding List/Collection type. Even though I actually define the constraint it still gives me a warning that I cannot remove. This isn't critical, but I really so heavily on working through all the warnings and not being able to clear the list is a little annoying.
let domainValue (domain:'Domain when 'Domain :> 'ItemDomain list) //Address Type Constraint Warning ?
: Value<'ItemDomain,'Domain> = (domain, (domain |> validDomainResult |> Some)) ||> value
Your constraint is saying that the argument should be a type 'Domain such that 'Domain :> list<'ItemDomain>, i.e. a type that has list<'ItemDomain> as a base class.
However, list<'T> is a sealed class and so there can be no type other than list<'ItemDomain> that would satisfy the constraint - the only type that satisfies the constraint is list<'ItemDomain> and so you could as well simplify the type declaration and use:
let domainValue (domain:list<'ItemDomain>) = ...
It is worth noting that this is not the case for interfaces like seq<'T> and so the following gives no error:
let domainValue (domain:'Domain when 'Domain :> seq<'ItemDomain>) = ...
This will allow arguments that are any kind of collection of 'ItemDomain values including arrays, lists, etc. Also, you can write the same constraint more concisely using # type, which means the same thing:
let domainValue (domain:#seq<'ItemDomain>) = ...

Value restriction - The value has been inferred to have generic type

Give the following definition
let fn (id: int) (_:string) = id
I can create a partially applied function
let fnPartial = fn 1
However changing the type of _ to a non sealed type like IEnumerable
let fn (id: int) (_:IEnumerable) = id
Causes a compilation error
Value restriction. The value 'fnPartial' has been inferred to have
generic type
val fnPartial : ('_a -> int) when '_a :> IEnumerable Either make the arguments to 'fnPartial' explicit or, if you do not intend
for it to be generic, add a type annotation. (using built-in F#
compiler)
A bug was raised but closed with the following response
Yes this is by design - IEnumerable is not sealed where string is, and
this causes the value restriction to trigger
The work around is to add a type annotation
let fn (id: int) (_:IEnumerable ) = id
let fnPartial<'a> = fn 1
Can someone explain
Whats the crux of the issue
How does adding a type annotation fix the issue
The key is that values are not allowed to be generic in F#. When you partially apply a function the result is a value.
In order to make the left hand side of a binding (or assignment) a function, you must define a parameter on the left hand side.
The error you're getting is a result of IEnumerable not being specific enough for the value to be completely defined. Given IEnumerable you do not know what you're iterating over, and therefore the compiler cannot determine a proper type for the value.
The answers to your question then are as follows:
The crux is the issue is that a value cannot be generic
Adding the type definition lets the compiler know that this is not a value, but rather is a function, or something which is allowed to be generic.
Here is the relevant MSDN Docs:
https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/dd233183(v=vs.100)#value-restriction

Is it possible to have an F# operator overload, on an internal type, available to all files in the assembly?

I have some types that enforce numeric value ranges, and I use them in many files in a single project. They look something like this:
[<Struct>]
type NonNegativeMoney =
new(x) =
if x < 0m then invalidArg "x" "Ruh-roh..."
{ Value = x }
val Value : decimal
static member (+) (x: NonNegativeMoney, y: NonNegativeMoney) = NonNegativeMoney(x.Value + y.Value)
I now want to make these types internal to the assembly and leave only my OO type model public. However, when I flip these types to internal, I got the following compiler error:
The member or object constructor 'op_Addition' is not public. Private
members may only be accessed from within the declaring type. Protected
members may only be accessed from an extending type and cannot be
accessed from inner lambda expressions.
The reason for this, has been addressed in the question Why does the F# compiler fail with this infix operator?. The solution proposed in the answer is to use F# signature files to make the types internal. This works for the OP's scenario in that question, where usage of the operator is limited to a type in the same file. However, I can't seem to find a way to make it work so the operator is accessible from all files in my project. If I use a signature file, it works intra-file, but not inter-file.
Is there any way to make this work so the types are internal to the assembly, but visible across the files in my project? I'd like to keep the operators, as I'm using library functions like Seq.sum that require them on the types being summed.
You can define the overload in a module instead of inside the type:
[<Struct>]
type internal NonNegativeMoney =
new(x) =
if x < 0m then invalidArg "x" "Ruh-roh..."
{ Value = x }
val Value : decimal
let internal (+) (x: NonNegativeMoney) (y: NonNegativeMoney) = NonNegativeMoney(x.Value + y.Value)
...but this will override the normal (+) operator, which is probably why you ruled it out. Using a custom operator (like ++) may be a reasonable compromise.
You can make the operator available to the whole project by marking the module [<AutoOpen>].

In F#, is it possible to pass a reference to a mutable, defaulted value as a parameter?

For the Froto project (Google Protobuf in F#), I am trying to update the deserialization code from using 'a ref objects to passing values byref<'a>, for performance.
However, the code below fails on the hydrator &element field line:
type Field = TypeA | TypeB | Etc
let hydrateRepeated
(hydrator:byref<'a> -> Field -> unit)
(result:byref<'a list>)
(field:Field) =
let mutable element = Unchecked.defaultof<'a>
hydrator &element field
result <- element :: result
error FS0421: The address of the variable 'element' cannot be used at this point
Is there anything I can do to get this code to work without changing the signature of the hydrator parameter?
I'm very aware that I could use hydrator:'a ref -> Field -> unit and get things to work. However, the goal is to support deserializing into record types without needing to create a bunch of ref objects on the heap every time a record is deserialize.
Note that the following code is perfectly legal and has the same signature as the hydrator function declaration, above, so I'm unclear on what the problem is.
let assign (result:byref<'a>) (x:'a) =
result <- x
let thisWorks() =
let mutable v = Unchecked.defaultof<int>
assign &v 5
printfn "%A" v
I'll try to clarify what I was saying in my comments. You're right that your definition of assign is perfectly fine, and it appears to have the signature byref<'a> -> 'a -> unit. However, if you look at the resulting assembly, you'll find that the way it's compiled at the .NET representation level is:
Void assign[a](a ByRef, a)
(that is, it's a method that takes two arguments and doesn't return anything, not a function value that takes one argument and returns a function that takes the next argument and returns a value of type unit - the compiler uses some additional metadata to determine how the method was actually declared).
The same is true of function definitions that don't involve byref. For instance, assume you've got the following definition:
let someFunc (x:int) (y:string) = ()
Then the compiler actually creates a method with the signature
Void someFunc(Int32, System.String)
The compiler is smart enough to do the right thing when you try to use a function like someFunc as a first class value - if you use it in a context where it isn't applied to any arguments, the compiler will generate a subtype of int -> string -> unit (which is FSharpFunc<int, FSharpFunc<string, unit>> at the .NET representation level), and everything works seamlessly.
However, if you try to do the same thing with assign, it won't work (or shouldn't work, but there are several compiler bugs that may make it seem like certain variations work when really they don't - you might not get a compiler error but you may get an output assembly that is malformed instead) - it's not legal for .NET type instantiations to use byref types as generic type arguments, so FSharpFunc<int byref, FSharpFunc<int, unit>> is not a valid .NET type. The fundamental way that F# represents function values just doesn't work when there are byref arguments.
So the workaround is to create your own type with a method taking a byref argument and then create subtypes/instances that have the behavior you want, sort of like doing manually what the compiler does automatically in the non-byref case. You could do this with a named type
type MyByrefFunc2<'a,'b> =
abstract Invoke : 'a byref * 'b -> unit
let assign = {
new MyByrefFunc2<_,_> with
member this.Invoke(result, x) =
result <- x }
or with a delegate type
type MyByrefDelegate2<'a,'b> = delegate of 'a byref * 'b -> unit
let assign = MyByrefDelegate2(fun result x -> result <- x)
Note that when calling methods like Invoke on the delegate or nominal type, no actual tuple is created, so you shouldn't be concerned about any extra overhead there (it's a .NET method that takes two arguments and is treated as such by the compiler). There is the cost of a virtual method call or delegate call, but in most cases similar costs exist when using function values in a first class way too. And in general, if you're worried about performance then you should set a target and measure against it rather than trying to optimize prematurely.

F#: Why aren't option types compatible with nullable types?

Why aren't option types like "int option" compatible with nullable types like "Nullable"?
I assume there is some semantic reason for the difference, but I can't figure what that is.
An option in F# is used when a value may or may not exist. An option has an underlying type and may either hold a value of that type or it may not have a value.
http://msdn.microsoft.com/en-us/library/dd233245%28VS.100%29.aspx
That sure sounds like the Nullable structure.
Because of the runtime representation choice for System.Nullable<'T>.
Nullable tries to represent the absent of values by the null pointer, and present values by pointers to those values.
(new System.Nullable<int>() :> obj) = null
|> printfn "%b" // true
(new System.Nullable<int>(1) :> obj).GetType().Name
|> printfn "%s" // Int32
Now consider strings. Unfortunately, strings are nullable. So this is valid:
null : string
But now a null runtime value is ambiguous - it can refer to either the absence of a value or a presence of a null value. For this reason, .NET does not allow constructing a System.Nullable<string>.
Contrast this with:
(Some (null : string) :> obj).GetType().Name
|> printfn "%s" // Option`1
That being said, one can define a bijection:
let optionOfNullable (a : System.Nullable<'T>) =
if a.HasValue then
Some a.Value
else
None
let nullableOfOption = function
| None -> new System.Nullable<_>()
| Some x -> new System.Nullable<_>(x)
If you observe the types, these functions constrain 'T to be a structure and have a zero-argument constructor. So perhaps F# compiler could expose .NET functions receiving/returning Nullable<'T> by substituting it for an Option<'T where 'T : struct and 'T : (new : unit -> 'T)>, and inserting the conversion functions where necessary..
The two have different semantics. Just to name one, Nullable is an idempotent data constructor that only works on value types, whereas option is a normal generic type. So you can't have a
Nullable<Nullable<int>>
but you can have an
option<option<int>>
Generally, though there are some overlapping scenarios, there are also things you can do with one but not the other.
Key difference is that must test the option type to see if it has a value. See this question for a good description of its semantics: How does the option type work in F#
Again, this is from my limited understanding, but the problem probably lies in how each gets rendered in the IL. The "nullable" structure probably gets handled slightly different from the option type.
You will find that the interactions between various .Net languages really boils down to how the IL gets rendered. Mostof the time it works just fine but on occasion, it causes issues. (check out this). Just when you thought it was safe to trust the level of abstraction. :)

Resources