00:01
<rbuckton>

And I do think there's a world where enum could be extended to support ADTs, e.g.:

enum Message {
  Quit,
  Write(text),
  Move({x, y}),
}
00:01
<canadahonk>
Rob Palmer: do you have the link to the stdlib breakout session notes? nvm
00:04
<bakkot>
did you mean https://devblogs.microsoft.com/typescript/announcing-typescript-5-8-rc/#the---erasablesyntaxonly-option ?
00:08
<canadahonk>
📣 come back to the main plenary room for a summary of breakouts
00:09
<rekmarks>
did you mean https://devblogs.microsoft.com/typescript/announcing-typescript-5-8-rc/#the---erasablesyntaxonly-option ?
Another reason I love TypeScript is that they frequently know what I want—and give it to me—before I do
00:10
<sffc>
I'll take notes of the summaries of the presentatoins in https://docs.google.com/document/d/17u9l-TRdEasF5cKHJnTmcBUNRqGQpsYxLNraV8sChfw/edit?tab=t.0
00:13
<rekmarks>
Enum with its current TS implementation is considered an anti-pattern today, not to say that a native enum implementation in ES would not be much better (since most C-like languages have them)
+1, although current TS enums have some nice properties, they are ultimately inessential
00:16
<rbuckton>

I'd still like to hear feedback as to what exactly is considered an anti-pattern regarding enum? The two things I've heard have been:

  • Don't use enum in TS because it's not in ES
  • enum defaulting to numbers and not something like Symbol()
00:17
<ljharb>
(considering the alternative is manually defining a fixed set of forgeable strings, or unforgeable but inconvenient objects or symbols, and manually validating those)
00:17
<Ashley Claymore>
Maybe the merging too?
00:17
<rbuckton>
Merging? as in enum A { ... } enum A { ... }?
00:17
<Ashley Claymore>
I'd be suprised if a 262 version had that
00:18
<rbuckton>
I agree that's an anti-pattern and is something we probably wouldn't want to carry forward.
00:21
<bakkot>
enums being numbers is also bad, in my opinion
00:22
<bakkot>
it makes it too easy for adding new values to accidentally be a breaking change
00:23
<rbuckton>

Regarding Number vs String vs Symbol, my version of the proposal had a solution to that:

enum Color of String {
  Red, // "Red"
  Green, // "Geen"
  Blue, // "Blue"
}

enum Kind of Symbol {
  A, // Symbol("A')
  B, // Symbol("B")
}

enum BigFlags of BigInt {
  None, // 0n
  Flag1 = 1n << 0n,
  Flag2 = 1n << 1n,
}

// etc.
00:23
<bakkot>
I would be ok with them being strings but at that point you might as well just use strings
00:23
<ljharb>
it's still helpful to simplify validation and enumeration
00:24
<rbuckton>
Numeric enums are the most common in C-like languages, and being numbers might be necessary for TS compat.
00:24
<bakkot>
yeah the experience from C-like languages is one of the main reasons I am opposed to them being numbers
00:24
<rbuckton>
Due to how existing .d.ts files work, it would be almost impossible to change that default behavior for TS.
00:25
<bakkot>
understandable, and I wouldn't want to add enums which conflicted with TS, but the conclusion I'd draw is that we should not add enums to JS at all
00:27
<ljharb>
would a TS user notice the value of the enum in typical usage? or is it just that a JS consumer would be using it
00:27
<rekmarks>
Re: “enums are an antipattern”, I haven’t verified all of the claims in this myself (and it’s a bit sensationalist) but have experienced / agree with a plurality of them: https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh
00:30
<rekmarks>
would a TS user notice the value of the enum in typical usage? or is it just that a JS consumer would be using it
IME no
00:32
<rekmarks>
Re: “enums are an antipattern”, I haven’t verified all of the claims in this myself but have experienced / agree with a plurality of them: https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh
Note that I can’t replicate the enum type unsafety example in this and have not encountered it previously
00:37
<rbuckton>
Re: “enums are an antipattern”, I haven’t verified all of the claims in this myself (and it’s a bit sensationalist) but have experienced / agree with a plurality of them: https://dev.to/ivanzm123/dont-use-enums-in-typescript-they-are-very-dangerous-57bh
Some of those reasons are no longer true. I'll read through and follow up after dinner.
00:41
<canadahonk>
https://github.com/linusg/ecmascript-wiki
00:57
<rbuckton>
  1. Enum's emit code - is irrelevant if enum exists natively
  2. Numeric types unsafe - this has since been fixed
  3. String ENUM's are named types - this does seem to be an odd behavior, though its specific to TS type checking and would be irrelevant to an ES enum

There is an issue with TS enum where there are a few cases where an enum member value's emit can be type-directed, but I think that's fixable in TS and wouldn't impact an ES enum.

01:02
<rbuckton>
Also, I still think Number values make the most sense for the default, especially when working with Atomics (e.g., .load(), .xor(), etc.) and I/O (protocols, headers, etc.), not to mention bitmasks. I also imagine number comparisons to be generally more efficient than string comparisons. I imagine they're also more readily inlineable into something like a jump table for use with switch. Numbers generally just work in most places.
01:04
<rbuckton>
One "compatible" approach I've also considered is that an ES enum might require the of clause if there are any members that are missing initializers. TS enum could elide it (as it does today), but emit enum E of Number {} to JS, or in the case of auto-numbering, emit enum E { A = 0, B = 1, ... } and populate the auto-numbered enums during emit.
01:45
<Ashley Claymore>

do we need the of Type ? If an MVP was that the values had to be explicitly assigned, no auto-increment.

const MyFlags = enum {
   None: 0,
   A: 1,
   B: 2,
   AB: enum.A & enum.B
};
01:48
<Ashley Claymore>
as ADT wouldn't need them right?
01:48
<rbuckton>
The of Type syntax is a convenience for those who specifically prefer something like a string or symbol by default for enums. In those cases, the repetition of enum E { A = Symbol(), B = Symbol(), ... } would be unnecessarily unwieldy.
01:49
<rbuckton>
It also provides a convenient way to describe the defaulting behavior for both auto numbered enums as well as string and numeric enums: https://github.com/rbuckton/proposal-enum?tab=readme-ov-file#evaluation
01:50
<Ashley Claymore>
if it's a connivence could it be a follow on?
01:51
<rbuckton>
IMO, the above syntax is a non-starter. const x = enum certainly doesn't help the "erasable syntax" case for TS, and enum.A would prevent future use of enum for any other metaproperty, which was a blocking issue for the the class.x syntax as well.
01:52
<Ashley Claymore>
I'd personally be ok with the bare A resolving to the member
01:52
<rbuckton>
I would not, unfotunately.
01:53
<Ashley Claymore>
how comes? your early example had it bare
01:53
<rbuckton>
enum.A would have the exact same issue as class.A, and I've come around to agreeing with that position.
01:54
<rbuckton>
One of the motivations for an enum proposal would be to reduce the runtime syntax friction between TS and ES. Proposing a wholly different syntax does not address that concern and would make adoption very difficult.
01:57
<Ashley Claymore>
makes sense. but we potentially need the syntax to be at least slightly different if the semantics are not identical?
01:57
<rbuckton>

Also, the mechanism for explicit autonumbering as part of emit ends up not being truly "eraseable" as it requires the introduction of temporaries to handle initializers, i.e.:

import { x } from "./other.js";
enum E {
  A = x,
  B,
  C
}

This is also an example of the problematic type-directed emit in TS today that I hope to fix in the future. I cannot statically know what the value of x is without type checking, and thus could not reliably emit the auto-incremented values for B and C without additional overhead, i.e.:

var _a;
enum E {
  A = _a = x,
  B = ++_a,
  C = ++_a,
}
01:58
<rbuckton>
The of syntax is an invocation of an explicit mapper function that handles defining the default initializer of each value.
01:58
<rbuckton>
Ideally, I would prefer a native syntax where enum E {} is the equivalent of enum E of Number {}, and thus the syntax could be identical in both cases.
01:59
<rbuckton>
This would also allow those who have a specific preference around using strings or symbols to express that via, e.g., a linter, while preserving the predominant base case of using numbers.
02:02
<Ashley Claymore>
I'm probably in the minority, I prefer the values being explicit. Even if it is repetitive
02:03
<Ashley Claymore>
are there other languages where the enum delegates to a runtime protocol to construct the values?
02:03
<rbuckton>
The other caveat of TS vs ES enums to address would be the reverse mapping aspect of TS enums, which is great for diagnostics purposes but unreliable for serialization/deserialization use cases. That's why my version of the enum proposal offloads reverse mappings to a symbol protocol and is made accessible via the Enum API. It introduces a more reliable and formal mechanism for parsing and serializing.
02:03
<rbuckton>
Python does
02:04
<Ashley Claymore>
thanks, I'll check that out
02:05
<rbuckton>
from enum import Enum, auto
class E(Enum):
  A = auto()
  B = auto()
02:05
<Ashley Claymore>
the parts I like the most about the proposal is the self reference during initialisation, and the object being frozen.
02:05
<rbuckton>
Also, while not quite runtime, Go uses iota. C# enums use a similar auto-numbering mechanism to the one defined in TypeScript, but are also handled at compile-time.
02:09
<rbuckton>

The other benefit to a syntactic enum type would be future support for decorators for things like control over serialization, formatting, and marshalling, e.g.:

enum Position of Symbol {
  @JSONSerializer.Alias("emp")
  @JSONSerializer.SerializeAs("employee")
  Employee,

  @JSONSerializer.Alias("mgr")
  @JSONSerializer.SerializeAs("employee")
  Manager,

  ...
}
02:14
<rbuckton>
This is also a hard comparison to make, considering the ratio of dynamic to static languages. By nature of JS being dynamic, more things often need to be done at runtime or are expressed via runtime operations (i.e., Object.defineProperty, Reflect.*, etc.).
02:17
<rbuckton>
I also hold out hope that a syntactic enum might be able to establish an object shape for the enum declaration that could eventually be used by bytecode generators for specific performance tuning, much like how we hope the fixed shape nature of struct could be utilized. For example, a switch over cases of E.A, E.B could potentially inline those values if it is known that E is an enum with a fixed shape, and thus convert the switch to a jump table.
02:19
<rbuckton>
As mentioned in one of the talks today, that's not something we can depend on, but its potentially feasible w/o needing any kind of whole program optimization on the part of the bytecode generator.
02:19
<Ashley Claymore>
if enums are mostly static I can see minifiers being able to inline the values
02:19
<Ashley Claymore>
the constructors might make that too hard
02:19
<rbuckton>
That's already the case in tools like esbuild
02:19
<Ashley Claymore>
right, but there isn't a symbol protocol today
02:20
<rbuckton>
But that depends on whole program optimization. The enum-ness of E could be encoded into its map/shape and is thus available when collecting type feedback on a switch case
02:21
<Ashley Claymore>
doesn't need to be whole program, only need to see the two modules
02:22
<Ashley Claymore>
follow the import and look at the export
02:22
<rbuckton>
Are you referring to the @@toEnum symbol?
02:23
<Ashley Claymore>
I'm not sure if the proposal changed since I last looked
02:23
<Ashley Claymore>
I thought the of T looked up a symbol on T
02:23
<rbuckton>
We could make those frozen on built-in constructors, so for the most common use cases it can be determined statically. Edge cases using a custom @@toEnum would be slower, but thats pay-to-play. Also, the performance cost is only during startup.
02:24
<Ashley Claymore>
I thought we had a rule that we couldn't freeze intrinsics
02:24
<rbuckton>
I mean { writable: false, configurable: false }
02:25
<Ashley Claymore>
I'd have to defer to TG3 if that fits their model
02:25
<rbuckton>
And if that's not viable, then an opt-out if it differs from the expected value.
02:27
<Ashley Claymore>
I'd personally be Ok with the possible patterns being baked in statically, without ways to create custom constructors. For strings if someone wanted a constructor that created lowercase or uppercase strings for example doesn't feel like enough of a motivation to introduce the dynamic lookup
02:27
<Ashley Claymore>
number+string+symbol sounds more than enough :)
02:27
<rbuckton>
Also bigint
02:28
<Ashley Claymore>
interesting
02:28
<rbuckton>
Also flags, and eventually ADT enums
02:28
<Ashley Claymore>
flags are numbers though right
02:28
<rbuckton>
Yes, but the auto-numbering behavior differs
02:28
<Ashley Claymore>
is bigint for when needing a larger domain of possible flags?
02:29
<rbuckton>
Yes, if bigint were fast enough I'd have plenty of TS enums that I'd make into bigints. In TS we regularly have to worry about running out of space in an SMI
02:29
<Ashley Claymore>
I do see the wins, but I also feel like explicit assignment is unambiguious as to what will happen. It's "just JavaScript" :D
02:29
<Ashley Claymore>
and the repeitiveness feels like a small price to pay
02:30
<rbuckton>
Flags enums would autonumber as 0, 1 << 0, 1 << 1, 1 << 2, ...
02:30
<Ashley Claymore>
I really hope we get the enum proposal and it feels like that's more likely to happen if it's kept simpler
02:30
<rbuckton>
Explicit assignment just isn't backwards compatible with TS, unfortunately.
02:31
<Ashley Claymore>
could tooling help here, I'd happily write the codemod!
02:32
<Ashley Claymore>
also thanks for helping answer my queries, much appreciated
02:32
<rbuckton>
The problem is the thousands of existing .d.ts files. Hand-generated .d.ts files can contain enum E { A, B } even though we normally emit them with hardcoded initializers.
02:32
<Ashley Claymore>
gotcha
02:32
<rbuckton>
We have over a decade of legacy to deal with that ties our hands in many ways.
02:33
<Ashley Claymore>
so would need a way to mark the 'new' enums when generating .d.ts
02:33
<rbuckton>
No, generating a .d.ts is fine since we always generate with hard-coded initializers.
02:33
<rbuckton>
We have no control over hand-rolled .d.ts files
02:34
<Ashley Claymore>
I mean we'd preserve the auto-increment type inference for existing .d.ts
02:34
<rbuckton>
i.e., much of DefinitelyTyped, any code that's behind the firewall, etc.
02:35
<Ashley Claymore>
JS enums could be enum E #{} to differentiate?
02:35
<rbuckton>
That's only possible if we preserve auto-incrementing numbers for native enums by default.
02:36
<rbuckton>
Why though? What value does that bring? of T at least has some benefits.
02:36
<Ashley Claymore>
it's simpler
02:36
<Ashley Claymore>
no dynamic lookup
02:37
<rbuckton>
Why is dynamic lookup bad for enum E of T {} but ok for class C extends B {}?
02:37
<rbuckton>
I'd put forth that class C extends B is worse because it requires dynamic lookup during new
02:37
<Ashley Claymore>
because classes need to extend from an infinite set
02:38
<rbuckton>
Number is an infinite set.
02:38
<Ashley Claymore>
enums can be a few built in types
02:38
<Ashley Claymore>
I mean the set of types
02:38
<Ashley Claymore>
not the set of values
02:38
<Ashley Claymore>
we can't hard code that classes can only extend from number/string/symbol
02:38
<Ashley Claymore>
we can for enum
02:39
<rbuckton>
What good does hard coding it do? of T doesn't enforce any constraints on the values of each enum member, it only affects auto-numbering.
02:39
<Ashley Claymore>
it's simpler
02:39
<rbuckton>
You could say enum E of Number { A = "foo" }
02:40
<rbuckton>
I don't think this kind of simpler will pass muster for others on the committee.
02:40
<Ashley Claymore>
I think the complexity of of is also not aligned with the feedback we are currently getting in committee to keep proposals simpler
02:40
<Ashley Claymore>
for other proposals
02:40
<rbuckton>
"simpler" would be enum E { A, B } produces auto-numbered values. That is a tried and true implementation that matches what TS has been doing for a decade
02:42
<Ashley Claymore>
Lots of code uses plain object literals for enums today, the biggest downside is not being able to self reference
02:42
<rbuckton>
IMO, auto-numbering is a very important part of the feature and is something a large number of existing TS developers rely on.
02:43
<rbuckton>
And without some form of auto-numbering, we have no way of making enum work with a pure "erasable types" solution as it would still require additional downlevel emit.
02:43
<Ashley Claymore>
I'd be OK if un-initalized names were auto-incrementing
02:43
<Ashley Claymore>
strings would need explicit assignment
02:44
<rbuckton>
Yes, but ljharb and bakkot would not.
02:44
<rbuckton>
of is designed to find a compromise between those positions.
02:44
<ljharb>
i don’t mind auto increment. But i mildly prefer only explicit
02:45
<Ashley Claymore>
if of is the only way to resolve the comittee's constraints I get that. But I'd really like to be sure we've exhausted 'simpler' options.
02:45
<rbuckton>
The other benefit to of is to specify the backing type for auto-increment behavior, such that you could have enum E of BigInt { None, A, B } produce bigints instead of numbers.
02:46
<ljharb>
the value is imo in covering enumeration and validation, i don’t care about the one time that might have to be explicitly written. read > write and all that
02:46
<Ashley Claymore>
that benefit seems small (incrementing bigint)
02:46
<rbuckton>
enumeration and validation are important, but only a small piece of the pie.
02:47
<ljharb>
for me it’s like 99% of it
02:47
<bakkot>
My preference continues to be not having enums, rather than trying to rationalize TS enums as a JS feature.
02:47
<rbuckton>
Yes, but for me its maybe like 40% of it.
02:47
<ljharb>
what’s the rest? Surely it’s not “not having to type out a few numbers and/or strings”
02:48
<ljharb>
types i guess, but im assuming a JS context here :-)
02:48
<rbuckton>
My major use cases have been ordered ranges, bitflags/bitmasks, and binary serialization formats (network I/O, binary file/section headers, etc.)
02:49
<ljharb>
bitflags only work if you can increment by powers of two, and i don’t see how that’s doable automatically
02:49
<rbuckton>
SyntaxKind, for example, uses auto-numbering and pre-defined ranges for fast comparison of AST node kinds.
02:49
<ljharb>
and formats are strings which are explicit
02:50
<rbuckton>
Its doable with of.
02:50
<ljharb>
auto numbering does seem like it make unintentional breaking changes highly likely
02:50
<bakkot>
I would be enthusiastic about a native BitSet, possibly backed by an arraybuffer
02:51
<bakkot>
(not with syntax though)
02:51
<rbuckton>

You can do something similar in Go as well:

type Foo int32
const (
  FooA E = 1 << iota // E(1)
  FooB // E(2)
  FooC // E(4)
)
02:53
<rbuckton>
As would any unintentional change to your public API.
02:54
<ljharb>
sure. But those are harder to do by accident.
02:54
<bakkot>
Zig's support for fixed-width ints of any width, with packing into binary representations, is quite nice
02:55
<rbuckton>
I would argue that banning auto-numbering, or requiring of String or of Symbol, is something a lint rule could handle, but it isn't a rule I would enforce by default.
02:56
<ljharb>
implicitness should be either allowed or prohibited, it shouldn’t be pushed to linters
02:58
<rbuckton>
I would, in general, be fine with a MVP version that has no @@toEnum and explicitly checks if the expression passed to of is one of the built-in Number, String, Symbol, or BigInt constructors, as that leaves room for future extensibility (i.e., of Enum.BitFlags, of Enum.ADT, etc.)
02:58
<ljharb>
stuff it’s ok to push to linters is when it’s a preference; this is different imo. Autonumbering is either fine, or a footgun, so we’d have to make a call
02:58
<rbuckton>
I believe the implicitness should be allowed, and is essential for compatibility.
02:59
<rbuckton>
Even without of, you can always enforce explicitness if you need, but enum E { A, B } producing numbers would be essential for compatibility.
02:59
<ljharb>
what kind of compatibility?
02:59
<ljharb>
TS can transpile to explicit numbers
03:00
<rbuckton>
Not without running into problems with "legacy" .d.ts files hand written before native enums existed.
03:00
<rbuckton>
Its the TS version of "this is already legal JS"
03:02
<rbuckton>
You also can't transpile to explicit numbers without either whole program analysis or introducing temporary variables.
03:03
<Ashley Claymore>
I'm not following this bit still. Legacy .d.ts would only impact the types, not the emit right?
03:05
<rbuckton>
The types describe the runtime behavior.
03:05
<Ashley Claymore>
I mean, the existing interpretation of those files doesn't need to change
03:08
<rbuckton>
It does if we adopt an interpretation of enum for ES that requires explicit initializers, and then adopts some other behavior for auto-initialization later.
03:09
<rbuckton>

Either way, not having auto numbering is incompatible with "erasable types" (i.e., such as ts-blank-space) as we have to inject initializers, or manually auto-number as in the example I mentioned above:

var _a;
enum E {
  A = _a = x,
  B = ++_a,
  C = ++_a,
}
03:10
<Ashley Claymore>
ts-blank-space only erases the parts with no runtime semantics. So it would preserve enums as written
03:10
<Ashley Claymore>
as they would be valid 262
03:11
<rbuckton>
Maybe it gets us half way, and if you run with --erasableSyntaxOnly you have to explicitly number your enum values, but I'd still prefer to have auto-numbering.
03:11
<ljharb>
right, the point is to make enums non erasable
03:11
<rbuckton>
It would not be pleasant to introduce a new *Keyword entry into SyntaxKind and have to shift 250+ entries up one integer.
03:12
<ljharb>
prefer is fine, but then it’s not a compat issue?
03:12
<ljharb>
It would not be pleasant to introduce a new *Keyword entry into SyntaxKind and have to shift 250+ entries up one integer.
breaking changes are supposed to be unpleasant to add
03:12
<ljharb>
feature not bug
03:12
<rbuckton>
I wholeheartedly disagree
03:12
<ljharb>
why would it be relevant what number backs the ast nodes?
03:13
<rbuckton>
Because we group those nodes for fast comparisons.
03:13
<ljharb>
other than to consumers who wouldn’t want the numbers to shift
03:13
<ljharb>
you’re implying that neighboring integers compare faster in engines?
03:13
<rbuckton>
i.e., node.kind >= SyntaxKind.FirstKeyword && node.kind <= SyntaxKind.LastKeyword
03:13
<ljharb>
ah
03:13
<Ashley Claymore>
how comes we need a new syntax kind?
03:13
<ljharb>
so, good api design would suggest designing groups initially that had enough gaps to never run into that problem?
03:14
<bakkot>
I think the SyntaxKind use case is quite specific to TS's code base
03:14
<rbuckton>
Years ago it was suggested to write a function that converts them to ints, but that's only making the problem 100% worse and killing the performance of that check.
03:14
<rbuckton>
I assure you it isn't.
03:14
<Ashley Claymore>
alternative would be to add a new flag to the existing syntax kind node
03:14
<rbuckton>
That is terrible for memory usage.
03:17
<bakkot>
How many programs are making more than a dozen different tagged variants of one kind of thing?
03:17
<bakkot>
I have literally only ever done that when writing parsers or general purpose binary formats.
03:18
<rbuckton>
Anything using jsdom?
03:18
<rbuckton>
Any DSL involving a tree of disjoint nodes?
03:18
<bakkot>
Jsdom itself, yes. Things using it, no.
03:18
<bakkot>
How many programs are making DSLs?
03:18
<rbuckton>
Games in Unity, or any other JS framework?
03:18
<ljharb>
most programs don’t deal with a DSL or a tree in first party code
03:18
<Ashley Claymore>
wouldn't it only be an extra 64bits * number of enums in program? i.e. orders of magnitudes less impact than adding a prop to the identifier nodes
03:19
<rbuckton>
Storing an extra field on every Node just to group the kind of node? This is empirically terrible. We're regularly trying to claw back memory by removing fields we can infer rather than store.
03:20
<rbuckton>
And how would you store something like that on the enum itself without having to do some kind of comparison/hashtable lookup just to pull out the group?
03:20
<Ashley Claymore>
it's not every node, I think I'm not following the original issue
03:23
<rbuckton>
At least in TypeScript's case, ordered integer enum values are a major performance feature. Being able to do fast math on node.kind is essential. That would apply to any application focusing on performance optimization, not just the TS compiler.
03:24
<bakkot>
That does not apply to any application for using on performance, just those with dozens of variants of one kind. Which is very very unusual outside of a parser, which most JS programs do not contain.
03:24
<ljharb>
also even if the TS codebase can’t use native enums, that doesn’t mean it’s not a valuable feature for the majority of programs
03:25
<Ashley Claymore>
nicolo-ribaudo: explained it to me. I thought the new syntaxKind was to represent these new enum syntax.
03:26
<Ashley Claymore>
as opposed to just the general TS change of needing to add any new syntax
03:26
<Ashley Claymore>
caught up. sorry for confusion
03:26
<rbuckton>

I don't disagree, but I'm trying to thread the needle of:

  • convenient syntax
  • avoiding unnecessary compat risks
  • aligning with "erasable types"
  • fast math
  • allowing non-numeric enumerated values
03:27
<ljharb>
if all can be achieved then great. But some of those don’t matter to the majority of codebases, so they should be the first to be sacrificed if needed
03:28
<Ashley Claymore>
auto-increment in userland with AOT inlining could be done with a magic comment :D
03:28
<Ashley Claymore>
I agree not lovely to build a custom tool for the project
03:28
<Ashley Claymore>
but I think the case of these huge enums with 100s of values is rare
03:29
<bakkot>
Extremely rare
03:29
<rbuckton>
All of those bullets are currently satisfied by the current TS enum syntax. The only case that would not be would be something like "Symbol by default" or "String by default", which would break all but the last bullet point.
03:30
<rbuckton>
the of T syntax was suggested to make "Symbol by default" or "String by default" far easier.
03:30
<ljharb>
of T seems fine to me fwiw
03:30
<Ashley Claymore>
symbol and strings needing to be explicit assignment seems like an OK compromise
03:31
<rbuckton>
And that's exactly how current TS enums work (for String, at least. Symbol isn't currently supported, but could be)
03:31
<ljharb>
(implicit strings is actually fine with me too, there’s no footgun there)
03:31
<bakkot>
For almost all programs, the benefit of having something which does not make it trivial to introduce breaking changes outweighs the benefit of auto-incrementing values. So the default should not be auto incrementing integers.
03:32
<Ashley Claymore>
I like current TS enums! (minus the merging and not being 262)
03:33
<rbuckton>
In a world where you had Symbol by default how would that differ from auto-incrementing integers if you never explicitly tried to hard code the value of an enum?
03:33
<ljharb>
because the symbols and strings aren’t ordered
03:33
<rbuckton>
That doesn't matter
03:33
<ljharb>
The very ordering/grouping property you went with numbers is why it’s a footgun
03:33
<ljharb>
it’s the only thing that matters afaict?
03:34
<rbuckton>
If you consider the value to be a black box, which is what Symbol by default implies, then whether the integers are ordered is irrelevant.
03:34
<ljharb>
adding a thing shouldn’t change other things
03:34
<ljharb>
that’s the concern
03:34
<ljharb>
that concern only exists with auto increment, not with implicit values
03:34
<rbuckton>
the ordering/grouping property is a performance optimization, not a footgun. If they were Symbols by default, there is no way I could have the same performance optimization.
03:35
<ljharb>
it’s both
03:35
<rbuckton>
For "Symbol by default", the initialized values change on every application startup
03:35
<ljharb>
nobody objects to the perf part from what i can tell, but it’s inseparable from the footgun part.
03:35
<ljharb>
that’s unobservable tho since it’s just identity. So it’s fine.
03:36
<bakkot>
the ordering/grouping property is a performance optimization, not a footgun. If they were Symbols by default, there is no way I could have the same performance optimization.
Sure there would! It just wouldn't be the default.
03:37
<bakkot>
Even without enums you could do what I do and have a generated file with the mapping of kinda to integers and regenerate whenever it changes.
03:37
<bakkot>
This is such a specialized use case.
03:37
<rbuckton>
Symbol by default wouldn't work with shared memory multithreading, though Auto-numbering and strings by default would.
03:37
<bakkot>
*kinds not kinda
03:39
<rbuckton>
Auto-numbering works because the nature of shared struct correlation depends on the module resolution cache, so you can be fairly confident that the enum is evaluated in the same way in both the main and worker threads. Strings work because they would be the same. Symbols would not because they would result in unique values in each thread.
03:41
<rbuckton>
This requires something like a hashtable lookup to produce the integer for comparison, which has a major impact on performance. It wouldn't be a solution for this case.
03:43
<rbuckton>
e.g., you go from something that is essentially 5 >= 1 && 5 <= 15 to map[sym] >= 1 && map[sym] <= 15, or x = map[sym]; x >= 1 && x <= 15, which requires more steps and more stack space in a tight loop/hot path
03:43
<bakkot>
I think you misunderstand. The mapping is just like `export const DECL_KIND = 12`
03:44
<rbuckton>
That's a compatibility issue, and again blows up the first four bullet points above.
03:45
<bakkot>
Right. It only satisfies TS's very niche use case. But other use cases do not have the auto-incrementing constraint.
03:46
<bakkot>
Auto-incrementing is a very unusual thing to want and is harmful to most programs. We should not make it easy to reach for.
03:52
<rbuckton>
It's getting late. I need to think on this more and chat with my team tomorrow. Unfortunately, the auto-numbering concern has been the biggest blocker for years, IMO.
05:17
<rkirsling>
oh man, it hurts that someone has chosen REK for their signifier, given that those are my actual initials 😆
07:03
<rekmarks>
oh man, it hurts that someone has chosen REK for their signifier, given that those are my actual initials 😆
I would’ve picked REKM but I was told to pick three letters 😛
17:28
<snek>
can i come to the tg5 event if i didn't register?
17:31
<snek>
Michael Ficarra: ^
17:32
<Michael Ficarra>
yep
19:07
<Jack Works>

I'd still like to hear feedback as to what exactly is considered an anti-pattern regarding enum? The two things I've heard have been:

  • Don't use enum in TS because it's not in ES
  • enum defaulting to numbers and not something like Symbol()
auto-incrementing is bad because if library authors use it it becomes a footgun. at least it should be opt-in
19:10
<nicolo-ribaudo>

Potentially stupid question, but when we say "auto-increment should be opt-in", can it just be "opt-in" like this?

let counter = 0;
enum TokenType {
  Arrow = counter++,
  Identifier = counter++,
  ParenL = counter++,
  ParenR = counter++,
  // ...
}
19:11
<nicolo-ribaudo>

Or, for binary flags,

let counter = 1;
enum TokenType {
  Arrow = counter <<=1,
  Identifier = counter <<=1,
  ParenL = counter <<=1,
  ParenR = counter <<=1,
  // ...
  Paren = ParenL | ParenR
}
19:12
<Jack Works>

Potentially stupid question, but when we say "auto-increment should be opt-in", can it just be "opt-in" like this?

let counter = 0;
enum TokenType {
  Arrow = counter++,
  Identifier = counter++,
  ParenL = counter++,
  ParenR = counter++,
  // ...
}
maybe auto enum T {} or enum T with Number { a; } (compared to enum T { a = 1 })