Reputation: 6223
Before this gets marked as a duplicate: I'm aware that this question is related to various questions about compilation errors when using unit as a type argument. Some examples:
unit
treated differently by the F# type system when used as a generic interface argument?These are all running into a problem similar to this one:
type Interface<'a> =
abstract member MyFunc : unit -> 'a
let implementingInstance =
{ new Interface<_> with
member __.MyFunc () = () } // Compiler error!
From what I understand, the code does not compile because a unit-returning function gets compiled with void
return internally, which is an extra feature of the CLI rather than a type.
However! The following seems to satisfy the compiler:
type RecordVersion<'a> =
{ MyFunc : unit -> 'a }
let recordInstance =
{ MyFunc = ignore }
This also works if I replace ignore
with a lambda or a let
-bound module function.
To me, this is just another formulation of the exact same thing. (Though at odds with the F# design guidelines, which suggest to prefer interfaces over function-carrying record types.)
I'm interested in designing APIs whose users specify the behavior and types used. Therefore, I would like to avoid cases where unexpected and confusing compiler errors occur. But I'm not quite sure what to make of this. It looks like F#'s "functional" functions do treat unit as a type.
What are the exact conditions for such spurious errors with unit? Can I avoid them in my API by breaking the design guidelines and using records of functions instead of interfaces? (I wouldn't mind much, but I'm not sure if it solves the problem for good.)
Upvotes: 4
Views: 231
Reputation: 55184
I believe the rule is that a method that is statically known to have return type unit
will be compiled to a .NET method with return type void
in the .NET type system (by statically known, I mean in contrast to a generic method or a method on a generic type which uses a type parameter as the return type). At invocations, the compiler hides the distinction between methods that return void
and methods that return true unit
values at the CLR level.
The problem in your example occurs because properly implementing the generic interface actually requires a unit
return type at the CLR level (and the CLR does care about the distinction between unit
and void
). In other words, the problem occurs if and only if you want to override a method which returns a type parameter of a generic class by a method which is statically known to return unit
(based on substituting unit
for that type parameter). By override here, I mean either implementing abstract methods on classes or interfaces or overriding non-sealed methods on classes.
As Tamil points out, one way to work around this limitation is to ensure that you use F# functions instead of methods. Another workaround is to introduce an extra concrete class into the hierarchy which has a dummy generic type parameter (say the extra class is T<'unit>
), and to return Unchecked.defaultof<'unit>
instead of ()
wherever that would cause problems. Then you can derive an additional non-generic concrete class T
from T<unit>
and everything will work fine.
Upvotes: 3
Reputation: 11362
The difference between your working and non-working examples is that the non-working one is a method, and in this case (and AFAIK only this case) the F# compiler generates IL code that actually takes or returns void
. In the working case, it is a property of type Microsoft.FSharp.Core.FSharpFunc<unit, unit>
(aka unit -> unit
) which does not get "optimized" into something taking or returning void
.
So yes, using records does solve the problem for good. Another possibility would be to make an interface with a property:
type Interface<'a> =
// Note the parentheses to make this member a property rather than a method
abstract member MyFunc : (unit -> 'a)
let implementingInstance =
{ new Interface<_> with
// Must use an explicit `fun () ->` instead of a method arg list
member __.MyFunc = fun () -> () }
If your problem was with taking unit as an argument, then it could be solved by writing a method that takes (())
as argument (ie. an explicit unit
value, rather than an empty list of arguments). But for the return value I don't think there is any way to make the method work :/
Upvotes: 3