`unit` elimination logic can lead to unutterable & unimplementable signatures
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 22h
- Merged PRs (30d)
- 144
Description
(This is not new, is low-impact, is likely a known thing, and likely cannot be fixed, but I'm recording it anyway.)
The way `unit` elimination works in the compiler means that `(())` is sometimes equivalent to `()` and sometimes not (see, e.g., #16254).
Sometimes, both `(())` and `()` may compile to `void` or the absence of a parameter; other times, both may compile to `Microsoft.FSharp.Core.Unit`; other times still, `(())` may compile to `Microsoft.FSharp.Core.Unit` and `()` to the absence of a parameter.
An interesting corollary to this is that it is possible to define an overloaded method each of whose overloads [compiles differently](https://sharplab.io/#v2:DYLgZgzgPgLgngBwKYAIAqKAUBKFBeAWACgVSUBbJcgIyQCcUB9AOgFktc8OyB6HlBAFdqwAJYBjFADcA9qIAmKVjhQBvFAF9iZClVoMW7TDk7c+A4WMmyFSzAFUAdqJhMAhnQDmARlzqNQA) while having an identical F# type signature:
```fsharp
type T () =
member _.M () = () // public void M() { }
member _.M (()) = () // public void M(Unit _arg1) { }
```
It is thus impossible to specify a signature for this type and its method overloads that will compile — both overloads have the same F# signature:
```fsi
member T.M : unit -> unit
```
That means that it is also impossible to declare such overloads on an interface or abstract class in F#. This is another way of saying that it's impossible to write an F# signature describing the C# signature `void M(Unit _arg1)`.
None of these compile:
```fsharp
type U =
abstract M : unit -> unit
abstract M : unit -> unit
```
```fsharp
type U =
abstract M : unit -> unit
abstract M : (unit) -> unit
```
```fsharp
type U =
abstract M : unit -> unit
abstract M : _arg1:unit -> unit
```
It is possible to define such an interface in C#, however:
```fsharp
public interface U
{
void M();
void M(Microsoft.FSharp.Core.Unit _arg1);
}
```
Such a C# interface can then be implemented from F# like:
```fsharp
type T () =
interface U with
member _.M () = ()
member _.M (()) = ()
```
On the other hand, it's also possible to define an interface in C# like the following:
```fsharp
public interface U
{
void M(Microsoft.FSharp.Core.Unit _arg1);
Microsoft.FSharp.Core.Unit M();
}
```
This interface is impossible to implement in F#:

What does this all amount to? Not much, since changing the `unit` elimination logic or using parentheses for differentiation (`member M : unit -> unit` = `void M()`, `member M : (unit) -> unit` = `void M(Unit _arg1)`) as is done for tuples would be backwards-incompatible.
Too bad .NET didn't just use `unit` instead of `void` from day 1 🙂
Contributor guide
Assessment
This issue has not been assessed yet.