Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Tangentially -- use languages that have great enum support.

Well you knew what was coming -- Rust enums are excellent[0], and so are Haskell's[1] (try and spot the difference between an enum and a record type!)... But that probably won't help you at $DAYJOB.

A bit more on topic though -- I'd like to see a strong opinion on Option<SomeEnum> versus SomeEnum containing a Missing variant. I usually lean towards Option<SomeEnum> but I wonder if there are any devout zero value proponents.

I don't like how golang does/requires zero values, but in more expressive languages I do waver sometime between Option<T> and T w/ a "missing" indicator variant inside.

[0]: https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html

[1]: https://wiki.haskell.org/Type



> I'd like to see a strong opinion on Option<SomeEnum> versus SomeEnum containing a Missing variant.

Java $DAYJOB here. If I'm a caller, I probably won't realise that SomeEnum.Missing exists. I'll just see that a function accepts a SomeEnum (which I don't have) so I won't call it.


This is a good point -- Option<T>is much better in a fn signature for comprehension. This probably clinches it.

Funnily enough, Java 1.8 is what really made me start taking Haskell and languages with better type systems more seriously -- seeing that Option actually deserved to be basically everywhere broke my frame.


An Option<Enum> has the advantage that you con chain it with other Option/Result operations like map, and_then, etc. I think this would be called being "monadic".

The advantage of Enum with Missing variant is that it's more expressive, and that you don't have to nest access in the Some matching.

Generally I prefer Option<Enum>, and often you can match directly with Some(Variant) to avoid nested matches.


> map, and_then, etc. I think this would be called being "monadic".

Strictly speaking, I think providing "map" just makes it functorial. Monadic would need a flatmap. (In addition to the other functor and monad requirements, of course.)


So what would you call something like "it implements some common operations", like these?

For example, the Option and Result type both have functions like "map", they do the same thing just on different types. They're not quite generic in that sense, but on a high level they seem so.

Another example are reactive libraries. Everything is pegged into some common operations like map, take, and so on.


You're right, and I'd add on that monads only need pure and bind (>>=), das it!

    pure :: Monad m => a -> M a

    (>>=) :: Monad m => M a -> (a -> M b) -> M b
pure is sometimes called return and >>= is sometimes called bind and flat map.

I was unsure that bind was the same as flatmap but it looks like it is.


The "and_then" that they mentioned is the flatmap


Option is the better of the two. With a missing variant, there's no way to encode "infallibly successful". All call sites would have to check for a missing variant in all cases.


This. Enum with a “missing“ variant is basically the billion dollar mistake again… kinda.


Option/Maybe/Result is a Boolean in disguise.

It has the same problem as Booleans by supporting only two branches: single happy path and single unhappy path, when real life may have many paths, and it’s not always clear which of them are happy or unhappy (from the business logic point of view).

Even for purely technical errors, simple OK | ERROR isn’t enough, but should be something like OK | RETRY | UKNOWN_ERROR | FAILED.


Yup, definitely a case for enums there -- I don't think the rule is absolute (I'm not sure you should always use enums over booleans), but much of the time it seems to be true.


Python's enum support[0] seems quite comprehensive, as well.

The nice thing about a class is that when some bizarre edge case comes up (and whose Extract, Transform, Load process has not?) one can isolate the weirdness.

[0] https://docs.python.org/3/library/enum.html


Typescript is great for this.

    function operation(user: User, state: “active” | “inactive”): void
Boom. Done. Enum defined. You want people to use enums? Remove all context switching from the definition process.


That’s the worst way to define enums. Now you have to make sure your codebase is using the same string literals everywhere and carry the potentially long type definition to every parameter, variable or function return type. And no, compile-time checks won’t cover 100% of cases.


Obviously if this gets reused you need to put it in a central definition.


How wouldn’t it be reused? Besides, what will happen if you need to change a constant in the enum from „a“ to „b“? Compiler won’t help you to spot all the places like ˋˋˋif (value == „a“)ˋˋˋ, it will quietly become unreachable in runtime.


You underestimate TypeScript. Your example will give a compiler error:

    This comparison appears to be unintentional because the types '"a"' and '"b"' have no overlap.(2367)
https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAZiEAu...

It's features like these that make Python/Ruby/etc. type-checkers insufficient substitutes for TypeScript.

I think my example wasn't the best. Of course "active" | "inactive" would get reused. But if it's just a little function flag it could easily be a one-off.


The only place I believe this pattern is acceptable is option props in React components.

Otherwise, it becomes an absolute mess of “almost” defined correctly.


Ah I love Typescript (IMO JS is the best "scripting language" out there, and has been for a very long time, but that's a different discussion).

That said, I hate that typescript has many ways of doing it. That's the biggest problem.

There's:

    enum SomeEnum {
        First,
        Second,
    }

    const enum SomeConstEnum {
      A = 1,
      B = A * 2,
    }

    type SomeEnumType = "first" | "second";

    const SomeObjectThatIsAnEnum = {
        "first": 1
        "second": 2
    } as const

I always lean towards the `enum` keyword, because I am of the opinion that if you're optimizing for less generated JS (and enums are actually worth optimizing for in this way) you're probably using the wrong language to begin with (unless you're in the browser and have no choice, etc).


I've done things like this before: https://www.typescriptlang.org/play/?#code/PTAEAkFMBsAdIE4Gc...

The gist is: define your enum values as a const array. Then you can generate a TypeScript type based on whatever values you have in there. And, since it's an actual array, you can easily write guards / asserts.

So you end up with something easy to extend and also has compile-time and run-time checks.


Thanks for the snippet, it is INTENSE. Will see what I can learn from it — const array I definitely didn’t consider before!


TypeScript enums, from personal experience, should be avoided in cases where the value is stored.

The reason is that the compilation from `SomeEnum` to JS involves conversion to an object indexer[0].

Should you, in the future, need to (or accidentally) remove or change an enum option, this will fail with an index out of range exception at runtime that is not obvious to track down.

Put it another way: it leaves behind an artifact in the output JS that can be a source of exceptions.

[0] https://www.typescriptlang.org/play/?ssl=5&ssc=2&pln=1&pc=1#...


Sorry, can you explain this more? I somehow still don't understand.

How is this different from a const object style enum? Or are you more referring to the sum type enum as the better choice here to avoid this.

Generally, if you remove an enum variant, the compiler should warn you if you've used it anywhere else (though of course it can't figure out a dynamic usage)... But this seems like a problem mostly for dynamic code, where you'd probably write some sort of Enum.parse() or Enum.from() static method anyway?

If I'm understanding your comment correctly, the real problem is that when you store the value, remove one, and try to read it back out into the enum it will fail - but in the code I've written so far trying to construct the the enum object almost always consisted of using a function to validate anyway, these days I use zod but before I'd write stuff like:

    function isEnum(obj: unknown): obj is Enum {
      return obj && typeof obj === 'string' && Object.values(Enum).includes(obj)
    }


My understanding of the comment is that it’s in the context of saving an enumeration-object into a database.

Like Typescript might (for example) represent enums as integers like 1, 2, 3... at runtime.

What happens if you decide to store a “delivery status” enum in a database? You would just be saving a number into the database, which can be hard to understand.

If it’s a string, then the value stored in the database is clearer and doesn’t depend on you being lucky with the Typescript compiler that the same enum type is compiled to the same integers after new releases.

I only realised this issue now, but it’s what I understood after reading that comment.


This is also another problem and a reason to strictly stick to string type unions, IMO.

There may be a really good use case for the enum type, but I think in most cases, string type unions are clearer, easier, and less prone to errors.


Yeah, one of the annoyances I have with using Enums, even in other languages, is that its serialised value isn't always obvious. Something like direction.NORTH could serialise into "NORTH" or some integer, depending on the language and the implementation.

I like Typescript's union of string literals approach because its serialised value is never in question. I know exactly what "NORTH" serialises to.


In Java it is de facto standard to serialize enums as string, with exception of JPA where you can explicitly tell if it’s a string or a number. It also supports exotic mappings via explicit declarations. Typescript should have implemented enums with union type under the hood, enabling the use of constants instead of strings when passing/checking values.


    but in the code I've written so far trying to construct the the enum object almost always consisted of using a function to validate anyway
It's exactly this problem and it's not obvious for someone choosing an enum type that the underlying implementation in JS is an object indexer.


Typescript type system is probably gold standard, you can enforce so much statically


As far as gold standards go for typesystems... ML languages probably take it, with Haskell being the closest to widespread real world use.


It's the gold standard for languages you'll be able to use at work.


Same in Python, no?

def foo(bar: Literal["active", "inactive"])


What's the best Python type checker these days?

Seems like there are at least 4: Mypy, Pytype, Pyright, and Pyre


basedpyright:

https://github.com/DetachHead/basedpyright

(assuming you can't/don't use MSFT's pylance)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: