Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

F-bounded polymorphism, interface operators, and type inference in C#

+5
−0

C# language version 8.0 introduces limited support for static methods, operators, etc. in interfaces. However, there are still limitations. I was hoping to use the new language features to try a generic approach to abstract algebra with proper operator overloading, but I'm running into the limitations.

Ideally what I'd like is

public interface IRing<TRing> where TRing : IRing<TRing>
{
    public static TRing operator -(TRing a);
    public static TRing operator +(TRing a, TRing b);
    public static TRing operator -(TRing a, TRing b) => a + -b;
    public static TRing operator *(TRing a, TRing b);
}

which can be used as e.g.

static TRing EvaluatePolynomial<TRing>(TRing x, params TRing[] coeffsBE)
    where TRing : IRing<TRing>
{
    var y = x - x; // Initially zero
    foreach (var coeff in coeffsBE)
        y = y * x + coeff;
    return y;
}

There are two obvious problems: static members of interfaces need implementations, and operator overloads must use the enclosing type. The first is easy enough to work around:

public interface IRing<TRing> where TRing : IRing<TRing>
{
    TRing Negate();
    TRing Add(TRing addend);
    TRing Mul(TRing addend);

    public static TRing operator -(TRing a) => a.Negate(b);
    public static TRing operator +(TRing a, TRing b) => a.Add(b);
    public static TRing operator -(TRing a, TRing b) => a + -b;
    public static TRing operator *(TRing a, TRing b) => a.Mul(b);
}

But the second is problematic. If I try changing the type of the first argument of each operator overload to IRing<TRing>:

public interface IRing<TRing> where TRing : IRing<TRing>
{
    TRing Negate();
    TRing Add(TRing addend);
    TRing Mul(TRing multiplicand);

    public static TRing operator -(IRing<TRing> a) => a.Negate();
    public static TRing operator +(IRing<TRing> a, TRing b) => a.Add(b);
    public static TRing operator -(IRing<TRing> a, TRing b) => a + -b;
    public static TRing operator *(IRing<TRing> a, TRing b) => a.Mul(b);
}

then the generic EvaluatePolynomial compiles, but operations on specific implementations don't necessarily. This simple implementation:

public struct BigInt : IRing<BigInt>
{
    private BigInt(BigInteger value) { Value = value; }

    public static implicit operator BigInt(int value) => new BigInt(value);

    private readonly BigInteger Value;

    public BigInt Negate() => new BigInt(-Value);
    public BigInt Add(BigInt addend) => new BigInt(Value + addend.Value);
    public BigInt Mul(BigInt multiplicand) => new BigInt(Value * multiplicand.Value);

    public static void Test()
    {
        BigInt one = 1;
        BigInt negOne = -one;
        var two = one + one;
    }
}

gives me errors on -one and one + one because, apparently, the type inference doesn't recognise that BigInt can be implicitly coerced to IRing<BigInt> and so match the signatures of the operator overloads.

Is there a straightforward solution to this which doesn't require reimplementing the operator overloads in each implementing class or explicitly coercing BigInt to IRing<BigInt>?

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

1 comment thread

General comments (3 comments)

0 answers

Sign up to answer this question »