Thread overview
User-defined "opIs"
Sep 24, 2014
Meta
Sep 24, 2014
Adam D. Ruppe
Sep 24, 2014
Meta
Sep 25, 2014
ketmar
Sep 27, 2014
Marco Leise
Sep 28, 2014
Marc Schütz
Sep 28, 2014
Marco Leise
Sep 28, 2014
Marc Schütz
Sep 29, 2014
Marco Leise
September 24, 2014
The following code fails under DMD 2.065:

struct Test
{
    bool opBinary(string op: "is", T: typeof(null))(T val)
    {
        return false;
    }

    bool opBinaryRight(string op: "is", T: typeof(null))(T val)
    {
        return false;
    }
}

void main()
{
    auto t = Test();
    //Error: incompatible types for ((t) is (null)): 'Test' and 'typeof(null)'
    //assert(t !is null);
    //assert(null !is t);
}

Is this supposed to work, and if not, should an enhancement be made to allow it? This is stopping std.typecons.Proxy from being a true proxy. See the following:

struct Test
{
	int* ptr;
	mixin Proxy!ptr;
	
	this(int* p)
	{
		ptr = p;
	}
}

auto i = new int;
assert(i !is null);
auto t = Test(i);
//Error: incompatible types for ((t) !is (null)): 'Test' and 'typeof(null)'
//assert(t !is null);
September 24, 2014
On Wednesday, 24 September 2014 at 22:48:36 UTC, Meta wrote:
> Is this supposed to work, and if not, should an enhancement be made to allow it?

It is not supposed to work - the docs don't list is as overridable (http://dlang.org/expression.html look for "Identity expressions") - and I don't think it should work either. Consider:

class A { opIs() }

if(a is null)

If that were rewritten into if(a.opIs(null))... and a were null, you'd have some trouble. Putting in an automatic null pre-check would start to complicate that is currently a simple operator.
September 24, 2014
On Wednesday, 24 September 2014 at 23:08:26 UTC, Adam D. Ruppe wrote:
> On Wednesday, 24 September 2014 at 22:48:36 UTC, Meta wrote:
>> Is this supposed to work, and if not, should an enhancement be made to allow it?
>
> It is not supposed to work - the docs don't list is as overridable (http://dlang.org/expression.html look for "Identity expressions") - and I don't think it should work either. Consider:
>
> class A { opIs() }
>
> if(a is null)
>
> If that were rewritten into if(a.opIs(null))... and a were null, you'd have some trouble. Putting in an automatic null pre-check would start to complicate that is currently a simple operator.

I'm thinking more for structs, which can't be null. The only other option right now is to use alias this, but that won't work in Proxy's case. For classes, I agree it's tricky... I know we're trying to *remove* things from Object, but what if we added:

class Object
{
    static bool opIs(T, U)(inout(T) a, inout(U) b) inout
    {
        //Some implementation
    }
}

And calls to is on an object get rewritten as:

Object.opIs!(typeof(a), typeof(b))(a, b)

So `if (a is null)` becomes `if (Object.opIs(a, null))` only when a is a class. When it's a struct, it would just be rewritten as `<struct name>.opIs!T(val)`.
September 25, 2014
On Wed, 24 Sep 2014 22:48:35 +0000
Meta via Digitalmars-d <digitalmars-d@puremagic.com> wrote:

> Is this supposed to work, and if not, should an enhancement be made to allow it?
i don't think that ER is needed. `is` is non-overloadable by design. making it overloadable will bring in the requrements for something like `is_which_is_not_overloadable`. which in turn will be asked to make overloadable, which will bring... ah, and so on. ;-)

i see some little problems with Proxy here, though. well, Proxy is not ideal. ;-)


September 25, 2014
On 9/24/14 6:48 PM, Meta wrote:
> The following code fails under DMD 2.065:
>
> struct Test
> {
>      bool opBinary(string op: "is", T: typeof(null))(T val)
>      {
>          return false;
>      }
>
>      bool opBinaryRight(string op: "is", T: typeof(null))(T val)
>      {
>          return false;
>      }
> }
>
> void main()
> {
>      auto t = Test();
>      //Error: incompatible types for ((t) is (null)): 'Test' and
> 'typeof(null)'
>      //assert(t !is null);
>      //assert(null !is t);
> }
>
> Is this supposed to work, and if not, should an enhancement be made to
> allow it? This is stopping std.typecons.Proxy from being a true proxy.
> See the following:
>
> struct Test
> {
>      int* ptr;
>      mixin Proxy!ptr;
>
>      this(int* p)
>      {
>          ptr = p;
>      }
> }
>
> auto i = new int;
> assert(i !is null);
> auto t = Test(i);
> //Error: incompatible types for ((t) !is (null)): 'Test' and 'typeof(null)'
> //assert(t !is null);

You're looking at it the wrong way. 'is' is not overridable, and shouldn't be.

But the type of null should be overridable (or you should be able to tell the compiler "this type can be compared with null"). I believe there has been discussion on this in the past, can't remember where it led.

-Steve
September 27, 2014
I'm against overloading identity checking, too. Instead I would propose to change its definition to make Proxy structs work. The rules are currently:

  For class objects, identity is defined as the object
  references are for the same object. Null class objects can
  be compared with is.

  For struct objects, identity is defined as the bits in the
  struct being identical.

  For static and dynamic arrays, identity is defined as
  referring to the same array elements and the same number of
  elements.

  For other operand types, identity is defined as being the
  same as equality.

If we changed that to:

  A byte for byte comparison of both operands is performed.
  For reference types this is the reference itself.

Breakage is limited to current uses of "is" that clone the
behavior of "==", which a deprecation warning could catch.
And to comparisons of slices with static arrays. (You'd have
to write `dynamicArr is staticArr[]` then.)
This is all comprehensible since you need to be educated about
the difference between value types and reference types anyways.
What it breaks it makes good for by allowing these to compare
equal:

struct CrcProxy { ubyte[4] value; }

CrcProxy crc32a;
ubyte[4] crc32b;

assert (crc32a is crc32b); // Yes, it is byte identical.

If empowering structs to fully emulate built-in types is still on the agenda it might be the way of least resistance.

-- 
Marco

September 28, 2014
On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise wrote:
> I'm against overloading identity checking, too. Instead I
> would propose to change its definition to make Proxy structs
> work. The rules are currently:
>
>   For class objects, identity is defined as the object
>   references are for the same object. Null class objects can
>   be compared with is.
>
>   For struct objects, identity is defined as the bits in the
>   struct being identical.
>
>   For static and dynamic arrays, identity is defined as
>   referring to the same array elements and the same number of
>   elements.
>
>   For other operand types, identity is defined as being the
>   same as equality.
>
> If we changed that to:
>
>   A byte for byte comparison of both operands is performed.
>   For reference types this is the reference itself.

Maybe allow this only for types that somehow implicitly convert to each other, i.e. via alias this?

>
> Breakage is limited to current uses of "is" that clone the
> behavior of "==", which a deprecation warning could catch.
> And to comparisons of slices with static arrays. (You'd have
> to write `dynamicArr is staticArr[]` then.)
> This is all comprehensible since you need to be educated about
> the difference between value types and reference types anyways.
> What it breaks it makes good for by allowing these to compare
> equal:
>
> struct CrcProxy { ubyte[4] value; }
>
> CrcProxy crc32a;
> ubyte[4] crc32b;
>
> assert (crc32a is crc32b); // Yes, it is byte identical.
>
> If empowering structs to fully emulate built-in types is still
> on the agenda it might be the way of least resistance.

September 28, 2014
Am Sun, 28 Sep 2014 10:44:47 +0000
schrieb "Marc Schütz" <schuetzm@gmx.net>:

> On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise wrote:
> >   A byte for byte comparison of both operands is performed.
> >   For reference types this is the reference itself.
> 
> Maybe allow this only for types that somehow implicitly convert to each other, i.e. via alias this?

That sounds like a messy rule set on top of the original when
alias this does not represent all of the type. You have a
size_t and a struct with a pointer that aliases itself to some
size_t that can be retrieved through that pointer.
The alias this will make it implicitly convert to size_t and
the byte for byte comparison will happily compare two equally
sized varibles (size_t and pointer).
So how narrow would the rule have to be defined before it
reads:

  If you compare with a struct that consists only of one member
  that the struct aliases itself with, a variable of the type
  of that member will be compared byte for byte with the
  struct.

-- 
Marco

September 28, 2014
On Sunday, 28 September 2014 at 14:27:56 UTC, Marco Leise wrote:
> Am Sun, 28 Sep 2014 10:44:47 +0000
> schrieb "Marc Schütz" <schuetzm@gmx.net>:
>
>> On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise wrote:
>> >   A byte for byte comparison of both operands is performed.
>> >   For reference types this is the reference itself.
>> 
>> Maybe allow this only for types that somehow implicitly convert to each other, i.e. via alias this?
>
> That sounds like a messy rule set on top of the original when
> alias this does not represent all of the type. You have a
> size_t and a struct with a pointer that aliases itself to some
> size_t that can be retrieved through that pointer.
> The alias this will make it implicitly convert to size_t and
> the byte for byte comparison will happily compare two equally
> sized varibles (size_t and pointer).
> So how narrow would the rule have to be defined before it
> reads:
>
>   If you compare with a struct that consists only of one member
>   that the struct aliases itself with, a variable of the type
>   of that member will be compared byte for byte with the
>   struct.

Yeah, it wasn't a good idea. Somehow it felt strange to throw all type safety out of the window, but on the other hand, bit-level comparison is the purpose of `is`, which isn't typesafe to begin with.
September 29, 2014
Am Sun, 28 Sep 2014 14:43:03 +0000
schrieb "Marc Schütz" <schuetzm@gmx.net>:

> Yeah, it wasn't a good idea. Somehow it felt strange to throw all type safety out of the window, but on the other hand, bit-level comparison is the purpose of `is`, which isn't typesafe to begin with.

I have the same feeling. No matter how we do it, one of the comparisons will always be left behind:

1a) identity of references with same referred type
  - objects with same reference and common inheritance branch
  - static arrays and the full slice over them
  - pointers of same type
1b) reference identity after implicit casts per language rules
  - void* with reference type
  - void[] with typed array
2) raw bit level equality (includes 1a) and parts of 1b))
  - proxy struct with wrapped type
  - signed with unsigned types on the bit level
3a) logical / overridable equality for user defined types
  - comparison of uint.max with -1 results in false
  - safe type widening 1.0f with 1.0, -1 with -1L, char types
  - opEquals()
3b) 3a) with "C-style" equality for built-in types
  - equal after integer type promotion
  - equal after implicit cast

One can define these from other perspectives, and the bit-wise struct comparison may be seen as a default opEquals (which I do here) or as a raw bit equality.

"==" is doing 3b). "is" comprises 1a), 1b) and 3b) as a
fallback to be defined for all types.
Defining "is" as 2) would remove the overlap for both
operators in 3b), but loose type-safety in 1a+b) and disallow
comparison of `void[] is ulong[]` since their lengths
will differ even if they span the same memory area.

With two operators we can capture any two subsets which
sound logical on their own, but not all of the possible
notions of identity and equality. And since people already
wonder about "is" in D and "===" in JavaScript it is probably
not a good idea to add even more comparison operators.
So I conclude that we will have to live with that shortcoming.

-- 
Marco