Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unit elimination logic can lead to unutterable & unimplementable signatures #17611

Open
brianrourkeboll opened this issue Aug 26, 2024 · 1 comment
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Milestone

Comments

@brianrourkeboll
Copy link
Contributor

(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 while having an identical F# type signature:

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:

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:

type U =
    abstract M : unit -> unit
    abstract M : unit -> unit
type U =
    abstract M : unit -> unit
    abstract M : (unit) -> unit
type U =
    abstract M : unit -> unit
    abstract M : _arg1:unit -> unit

It is possible to define such an interface in C#, however:

public interface U
{
    void M();
    void M(Microsoft.FSharp.Core.Unit _arg1);
}

Such a C# interface can then be implemented from F# like:

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:

public interface U
{
    void M(Microsoft.FSharp.Core.Unit _arg1);
    Microsoft.FSharp.Core.Unit M();
}

This interface is impossible to implement in F#:

image

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 🙂

@brianrourkeboll
Copy link
Contributor Author

Ah, I knew I had seen an issue touching on this before: #673

Don seems to imply that it might be worth opening a language suggestion to distinguish between

unit -> unit

and

(unit) -> unit

as is done for tuples:

#673 (comment)

I agree that, in principle, this should be addressed using the syntax you mention so consider it pre-approved

#673 (comment)

Closing as this is really a language suggestion. Right now what we have is by design, but has this limitation

That would be a breaking change, though, as far as I understand.

@abonie abonie added Breaking-change Describes a bug which is also a breaking change. Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code. and removed Breaking-change Describes a bug which is also a breaking change. labels Sep 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Projects
Status: New
Development

No branches or pull requests

3 participants
@brianrourkeboll @abonie and others