Skip to content

The implicit tuple to Interval conversion invents closedness, and picks the opposite of what (a, b) reads as #867

Description

@Rafael-SOWNet

Entity has an implicit conversion from a two-element tuple that builds an Interval:

public static implicit operator Entity((Entity left, Entity right) interval)
    => new Interval(interval.left, true, interval.right, true);

Measured on master:

Entity e = ((Entity)1, (Entity)5);
// [1; 5]   left inclusive = True, right inclusive = True
// and 1 belongs to it

Two things wrong with that, and they compound:

It invents information. A tuple carries two endpoints and nothing about whether either is included. The conversion has to pick, and whatever it picks is not in the input. This is the same objection @Happypig375 raised in #861 about the array-to-set conversions, which were removed in #866 for exactly this reason:

converting an array to a set is inherently information-losing because it removes order and duplicate elements. This shouldn't even be a conversion at all; explicit set construction is used for basically all the set types out there.

Order and duplicates going missing, and closedness appearing out of nowhere, are the same defect seen from opposite ends: the conversion and its input disagree about what information exists.

It picks the opposite of how the syntax reads. (1, 5) is the open interval in essentially all mathematical notation — [1, 5] is the closed one. So a caller writing what looks like an open interval gets a closed one, and the endpoints they meant to exclude are members. Nothing warns them.

Suggested fix

Remove the conversion. The explicit forms already exist, read unambiguously, and are what the notation problem calls for:

Entity e = MathS.Interval(1, 5);                  // the closed one, said out loud
Entity e = MathS.Interval(1, false, 5, false);    // the open one
Entity e = new Interval(1, true, 5, true);

Changing the conversion to produce an open interval instead would be worse: it silently flips the meaning of existing code, and still invents the closedness.

Why now

Removing a public conversion is breaking, so it wants the 2.0 window while it is still in preview — the same reason #861 was filed as a question rather than left as a comment. After 2.0 it waits for 3.0.

The blast radius is worth measuring before deciding; for the array conversion it turned out to be exactly one place in the repository. I have not measured this one yet.

Not part of this

The audit that turned this up also looked at the numeric conversions, which are fine — a widening from any integer or float type keeps its value. One that looked suspicious does not reproduce: Real.op_Implicit(ERational) has a lossy branch when DowncastingEnabled is off, but Entity e = someERational does not reach it, giving an exact Rational either way, and asking specifically for a Real with downcasting disabled is arguably a request for a decimal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Opinions wantedWe are interested in your opinion about the topicProposalContribute to the project by proposing some improvements

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions