dart-lang / language

Design of the Dart language
Other
2.67k stars 205 forks source link

Shorthand for creating constant identifier var (like an enum with a single value) #694

Open icatalud opened 5 years ago

icatalud commented 5 years ago

If I understood it correctly, this is what is being asked for in #183.

Sometimes I have required a constant value where I don’t care about the value, but I want it to be unique (as if it were a single value enum), so I create a var for the static type safety convenience. This has been useful for creating constant map keys or in #183 there is another use case. Currently this can be done either by creating an enum type (which can be a bit clumsy) or by defining a var:

static var myIdentifier = Object() (static when inside objects)

The drawback of this is that myIdentifier cannot be declared constant, it is a bit verbose and a prone to errors if forgetting to write static. It would be useful if it were possible to create constant identifiers like:

symbol id1, id2, id3;
Map map = {id1: 0};
foo(id1);
print(map[id3]);   // prints '2'
...
foo(symbol id) {
  map[id]++;
// id1 != id when symbol comes from the outside, only way that they can be equal is when foo(id1) 
// is invoked from within foo
  symbol id1; 
  map[id1] = 0;
  map[id2] = map[id1] + 1;
  map[id3] = map[id2] << 1;
}

symbol should be scoped as any var but it remains constant within the scope of a class or method.

I use the word symbol as an illustration, but I do not think it is appropriate as it conflicts with Symbol. When I required this feature I investigated and arrived to the Symbol type, however Symbol type didn’t make much sense to me, I think doing #mySymbol should just be a shorthand for creating a single word string, so #mySymbol == 'mySymbol'. I read a similar comment on #251. Would it be possible to change the Symbol behavior in future Dart releases?

lrhn commented 5 years ago

So symbol id; would introduce an identifier id bound to a unique value per declaration. The value would be the same each time the same declaration is evaluated, but different between different declarations. In practice it is always treated like a static declaration, even if it occurs in a non-static context.

The value of the identifier could be something like the Capability class from dart:isolate, but also usable as a compile-time constant.

Effectively it is as if there is a class UniqueObject with enough state that each instance of it can be globally unique (say, parameterize it by a library URI string and a file position), so symbol id becomes const UniqueObject id = UniqueObject._(currentLibraryUriString, positionInFile);.

icatalud commented 5 years ago

Effectively it is as if there is a class UniqueObject with enough state that each instance of it can be globally unique (say, parameterize it by a library URI string and a file position), so symbol id becomes const UniqueObject id = UniqueObject._(currentLibraryUriString, positionInFile);.

Yes, I think UniqueObject is what should be incorporated, because doing const Object() does not work as it is the same object everywhere. This issue was discussed here. Giving open access to UniqueObject would allow creating unique const constructors. I think “unique id” is more suitable than “symbol id” as a shorthand for “var id = const UniqueObject()”.

More general

A more general approach would be to create an additional keyword unique that can be used instead of const. Like:

var c1 = const [1, 2, 3];
var c2 = const [1, 2, 3];
assert(c1 == c2);

var u1 = unique [1, 2, 3];
var u2 = unique [1, 2, 3];
assert(u1 != u2 && u1 != c1 && u2 != c1);

unique id;   // inferred as var o = unique Object();
lrhn commented 5 years ago

That's an interesting idea - compile-time constant without canonicalization.

Dart constants are a composite of three or four features (depending on how you count):

and there are use-cases where you need a constant value (mainly for creating other constants).

Users use the features for one or more of these features. What you are asking for here is a way to get compile-time evaluation (one value per expression, not one per time it is evaluated) and deep-immutability (maybe?), but without canonicalization.

It is an interesting idea to see if we can split the const feature into its component parts.

The "static expression" proposal is a way to ask for one-time evaluation. An expression of the form static e would evaluate only the first time it's encountered, caching the value for next time (like a lazily initialized variable, which is a valid implementation strategy).

This proposal asks for a non-canonicalizing, and compile-time evaluated expression to be allowed where const expressions are required.

Maybe there are other combinations that also make sense.

icatalud commented 5 years ago

What you are asking for here is a way to get compile-time evaluation (one value per expression, not one per time it is evaluated) and deep-immutability (maybe?), but without canonicalization.

That sums it up. They should definitely be deep-immutable. I don't know how the internals are implemented, but I would expect that underneath the compiler forcefully creates a new const when the unique keyword is used (it doesn't try to reference an already created const). Every const constructor should be able to be invoked as either const or unique.

I'm thinking that somehow it could be a useful thing that with this unique keyword class methods could be related to unique symbols.

leafpetersen commented 4 years ago

cc @jacob314 who has a related use case.

icatalud commented 4 years ago

Concreting this feature:

unique id;    // unique id = Symbol('id');
unique obj = Object();
unique id2 = Symbol('id');
assert(id!=id2);

// invalid
unique foo = 'foo'    // natives types cannot be unique

// it should also be possible to shortcut
map[unique id] = 55;
print(map[id]);