Discussion:
User-defined "opIs"
Meta via Digitalmars-d
2014-09-24 22:48:35 UTC
Permalink
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);
Adam D. Ruppe via Digitalmars-d
2014-09-24 23:08:25 UTC
Permalink
Post by Meta via Digitalmars-d
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.
Meta via Digitalmars-d
2014-09-24 23:29:23 UTC
Permalink
On Wednesday, 24 September 2014 at 23:08:26 UTC, Adam D. Ruppe
Post by Adam D. Ruppe via Digitalmars-d
Post by Meta via Digitalmars-d
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
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)`.
ketmar via Digitalmars-d
2014-09-25 00:02:54 UTC
Permalink
On Wed, 24 Sep 2014 22:48:35 +0000
Post by Meta via Digitalmars-d
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. ;-)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20140925/424a0c54/attachment.sig>
Steven Schveighoffer via Digitalmars-d
2014-09-25 12:15:14 UTC
Permalink
Post by Meta via Digitalmars-d
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.
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
Marco Leise via Digitalmars-d
2014-09-27 11:48:00 UTC
Permalink
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
via Digitalmars-d
2014-09-28 10:44:47 UTC
Permalink
Post by Marco Leise via Digitalmars-d
I'm against overloading identity checking, too. Instead I
would propose to change its definition to make Proxy structs
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.
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?
Post by Marco Leise via Digitalmars-d
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
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 Leise via Digitalmars-d
2014-09-28 14:37:04 UTC
Permalink
Am Sun, 28 Sep 2014 10:44:47 +0000
Post by via Digitalmars-d
Post by Marco Leise via Digitalmars-d
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
via Digitalmars-d
2014-09-28 14:43:03 UTC
Permalink
Post by Marco Leise via Digitalmars-d
Am Sun, 28 Sep 2014 10:44:47 +0000
On Saturday, 27 September 2014 at 11:38:51 UTC, Marco Leise
Post by Marco Leise via Digitalmars-d
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
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.
Marco Leise via Digitalmars-d
2014-09-29 10:12:13 UTC
Permalink
Am Sun, 28 Sep 2014 14:43:03 +0000
Post by via Digitalmars-d
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
Continue reading on narkive:
Search results for 'User-defined "opIs"' (Questions and Answers)
11
replies
Provide the most amazing palindrome?
started 2006-04-05 02:37:56 UTC
languages
Loading...