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

Method overload resolution using pattern matching #1468

Closed
jhickson opened this issue Apr 19, 2018 · 6 comments
Closed

Method overload resolution using pattern matching #1468

jhickson opened this issue Apr 19, 2018 · 6 comments

Comments

@jhickson
Copy link

I'd like to propose adding support for allowing a form of method overload resolution using pattern matching. The form this would take is similar to that in functional languages such as Elixir. It would essentially extend the pattern matching syntax supported in C# 7 for switch case statements to methods. I feel sure this must have been proposed before, but I can find no issue relating to it.

Example

Consider a simple implementation of equality for a struct:

public struct MyStruct : IEquatable<MyStruct>
{
    private readonly int _value;

    public bool Equals(MyStruct other) => _value == other._value;

    public override bool Equals(object obj)
    {
        if (obj is null)
        {
            return false;
        }

        return obj is MyStruct value && Equals(value);
    }

    /* ... other methods ... */
}

This could be rewritten as:

public struct MyStruct : IEquatable<MyStruct>
{
    private readonly int _value;

    public bool Equals(MyStruct other) => _value == other._value;

    // A cast may be required to disambiguate the overload signature
    public override bool Equals((object)null) => false;

    public override bool Equals(object obj) when (obj is MyStruct value)
        => Equals(value);

    public override bool Equals(object obj) => false;

    /* ... other methods ... */
}

Implementation

Conceptually, the way I would see this working is that the three versions of Equals(object obj) would be resolved to a single method along the lines of

    public override bool Equals(object obj)
    {
        switch (obj)
        {
            case null:
                return false;
            case MyStruct value:
                return Equals(value);
            default:
                return false;
        }
    }
  • The order of declaration indicates matching priority;
  • A "default" implementation should always be required - i.e. a catch-all implementation without filters;
    • the compiler should warn of unreachable code should an implementation with filters be placed after the catch-all implementation.

Advantages

I believe in many cases this would make code more readable and less verbose, in the same way that exception filtering and switch case pattern matching has.

It seems to me there would also be the opportunity for optimizations when a method is called with arguments whose values are known at compile time. For instance, consider the following:

    public int Product(1, 1) => 1;

    public int Product(int x, int y) => x * y;

    public void Foo()
    {
        ...

        var bar = Product(1, 1);

        ...
    }    

Here the assignment to bar is via a call to Product(,) featuring compile-time constants which matches to an implementation returning a compile-time constant, so presumably the compiler could replace the call with var bar = 1. The example above is very trivial, but I could envisage in other usages this leading, via recursive calls, for instance, to compile-time function evaluation. That's either an advantage or opening the gates of hell, depending on your point-of-view.

@HaloFour
Copy link
Contributor

Relevant: dotnet/roslyn#955

@jnm2
Copy link
Contributor

jnm2 commented Apr 19, 2018

The team might come to the same conclusion as they did with #581 (comment).

@svick
Copy link
Contributor

svick commented Apr 19, 2018

I don't think this kind of syntax makes sense in a language like C#, which has full-blown method overloading and where method declarations can be fairly verbose.

Method overloading is an issue, because your syntax makes it hard to distinguish separate overloads. As far as I know, languages that support this kind of syntax either don't have method overloading (like Haskell) or have only limited support for it (like Elixir).

Verbosity of methods declarations, due to modifiers like public and override, but also due to not having type inference as strong as most functional languages, is an issue, because it means you would have to repeat yourself quite a lot with this syntax.

I think a much better option in this case is a switch expression (planned for C# 8.0). With that, the method would look like this:

public override bool Equals(object obj) =>
    obj switch
    {
        null => false,
        MyStruct value => Equals(value),
        _ => false
    };

This is shorter than your version, and I'd say it's also easier to understand.

@bondsbw
Copy link

bondsbw commented Apr 20, 2018

This could be useful with partial classes and inheritance scenarios.

@bondsbw
Copy link

bondsbw commented Apr 20, 2018

If implemented I would prefer a more consistent syntax.

Perhaps:

public override bool Equals(object obj) when (obj is (object)null)
    => false;
public override bool Equals(object obj) when (obj is MyStruct value)
    => Equals(value);
public override bool Equals(object obj) => false;

Or:

public override bool Equals(object obj when obj is (object)null)
    => false;
public override bool Equals(object obj when obj is MyStruct value)
    => Equals(value);
public override bool Equals(object obj) => false;

(I'm not specifically against Equals((object)null).)

@jhickson
Copy link
Author

@HaloFour, @jnm2 thanks for pointing me to those. Given their conclusions, this is unlikely to be accepted.

@svick I wasn't aware of switch expressions and, I agree, that's somewhat clearer than a raft of overloads.

Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants