Dart int and double being interned? Treated specially by identical()?
Asked Answered
T

3

5

Dart has both:

  • an equality operator == and
  • a top-level function named identical().

By the choice of syntax, it feels natural to want to use Dart's == operator more frequently than identical(), and I like that. In fact, the Section on Equality of the Idiomatic Dart states that "in practice, you will rarely need to use" identical().

In a recent answer to one of my questions concerning custom filters, it seems that Angular Dart favors use of identical() rather than == when trying to determine whether changes to a model have reached a steady state. (Which can make sense, I suppose, for large models for reasons of efficiency.)

This got me to thinking about identity of int's and so I wrote some tests of identical() over ints. While I expected that small ints might be "interned/cached" (e.g. similar to what is done by Java's Integer.valueOf()), to my surprise, I can't seem to generate two ints that are equal but not identical. I get similar results for double.

Are int and double values being interned/cached? Or maybe identical() is treating them specially? Coming from a Java background, I used to equate equate Dart's:

  • == to Java's equal() method and
  • identical() to Java's equality test ==.

But that now seems wrong. Anyone know what is going on?

Td answered 27/1, 2014 at 20:26 Comment(1)
identical checks reference, == checks value equality. If you're having problems creating two int's that are the same value, but not identical, it means that your variables are actually sharing the same reference, which would make sense if your compiler is optimizing code, realizing that the values will be exactly the same.Calvities
T
3

Seems like I posted too quickly. I just stumbled on Dart Issue 13084: Spec says identical(1.0, 1) is true, even if they have different types which led me to the Dart section on Object Identity of the language spec. (I had previously search for equality in the spec but not object identity.)

Here is an excerpt:

The predefined dart function identical() is defined such that identical(c1, c2) iff: 
- c1 evaluates to either null or an instance of
  bool and c1 == c2, OR 
- c1 and c2 are instances of int and c1 == c2, OR
- c1 and c2 are constant strings and c1 == c2, OR 
- c1 and c2 are instances of double and one of the following holds: ...

and there are more clauses dealing with lists, maps and constant objects. See the language spec for the full details. Hence, identical() is much more than just a simple test for reference equality.

Td answered 27/1, 2014 at 20:47 Comment(0)
E
5

Numbers are treated specially. If their bit-pattern is the same they must be identical (although it is still debated if this includes the different versions of NaNs).

The main reasons are expectations, leaking of internal details and efficiency.

Expectations: users expect numbers to be identical. It goes against common sense that x == y (for two integers) but not identical(x, y).

Leaking of internal details: the VM uses SMIs (SMall Integers) to represent integers in a specific range (31 bits on 32-bit machines, 63 on 64-bit machines). These are canonicalized and are always identical. Exposing this internal implementation detail would lead to inconsistent results depending on which platform you run.

Efficiency: the VM wants to unbox numbers wherever it can. For example, inside a method doubles are frequently moved into registers. However, keeping track of the original box can be cumbersome and difficult.

foo(x, y) {
  var result = x;
  while(y-- > 0) {
    result += x;
  }
  return result;
}

Suppose, that the VM optimizes this function and moves result into a register (unboxing x in the process). This allows for a tight loop where result is then efficiently modified. The difficult case happens, when y is 0. The loop wouldn't execute and foo would return x directly. In other words, the following would need to be true:

var x = 5.0;
identical(x, foo(x, 0));  // should be true.

If the VM unboxed the result variable in the method foo it would need to allocate a fresh box for the result and the identical call would therefore return false.

By modifying the definition of identical all these problems are avoided. It comes with a small cost to the identical check, though.

Encumber answered 28/1, 2014 at 8:57 Comment(2)
Great in depth view of the situation. Thanks. Although I was surprised to have my mental model thrown off, I much prefer the given Dart semantics of identical.Td
As a user you can think of it as if the VM canonicalized all numbers. In theory it can even do that as an optimization (for example during garbage collection). In some JavaScript VMs, doubles are primitives (see "fat pointers"), in which case this would already be true for doubles.Encumber
T
3

Seems like I posted too quickly. I just stumbled on Dart Issue 13084: Spec says identical(1.0, 1) is true, even if they have different types which led me to the Dart section on Object Identity of the language spec. (I had previously search for equality in the spec but not object identity.)

Here is an excerpt:

The predefined dart function identical() is defined such that identical(c1, c2) iff: 
- c1 evaluates to either null or an instance of
  bool and c1 == c2, OR 
- c1 and c2 are instances of int and c1 == c2, OR
- c1 and c2 are constant strings and c1 == c2, OR 
- c1 and c2 are instances of double and one of the following holds: ...

and there are more clauses dealing with lists, maps and constant objects. See the language spec for the full details. Hence, identical() is much more than just a simple test for reference equality.

Td answered 27/1, 2014 at 20:47 Comment(0)
G
1

I can't remember the source for this, but somewhere on dartlang.org or the issue tracker it was said that num, int and double are indeed getting special treatment. One of those special treatments is that you can't subclass those types for performance reasons, but there may be more. What exactly this special treatment entails can probably only be answered by the developers, or maybe someone who knows the specification by heart, but one thing can be inferred:

The numeric types are dart objects - they have methods you can call on their instances. But they also have qualities of primitive data types, as you can do int i = 3;, while a pure object should have a new keyword somewhere. This is different from Java, where there are real primitive types and real objects wrapping them and exposing instance methods.

While the technical details certainly are more complex, if you think about dart numerics as a blend of object and primitive, your comparison to Java still makes sense. In Java, new Integer(5).equals(new Integer(5)) evaluates to true, and so does 5==5.

I am aware this is not a very technically correct answer, but I hope it's still useful to make sense of the behaviour of dart numerics when coming from a Java background.

Goldman answered 27/1, 2014 at 20:45 Comment(1)
Hi MarioP. You are right about int, double etc being treated specially. I just found the section in the language spec confirming that.Td

© 2022 - 2024 — McMap. All rights reserved.