-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Syntactic sugar for enum types #627
Comments
Indeed disjoint unions would be the recommendation for enums (and I'd say typically string literals will be more descriptive than numbers). Keeping this open to track documentation around this as suggested |
Sorry to pollute the thread but is there any enums already implemented in Flow ? I can't find any doc about this. |
@guizmaii |
Thanks. How do you use it ? |
@samwgoldman The downside to that is you cannot do |
@stri8ed +1 |
This works nicely: const MyEnum = {
FOO: 'FOO',
BAR: 'BAR'
};
type MyEnumType = $Keys<typeof MyEnum> |
Would it be possible in the future for flow type defs to refer to const FOO = 'FOO';
const BAR = 'BAR';
type MyEnum = FOO | BAR; |
const Qos = {
AT_MOST_ONCE: 0,
AT_LEAST_ONCE: 1,
EXACTLY_ONCE: 2,
DEFAULT: 3,
};
type QosType = $Keys< typeof Qos>;
class Test{
qos:QosType = Qos.AT_LEAST_ONCE;
} will have this error: 27: qos:QosType = Qos.AT_LEAST_ONCE; |
@timzaak yes, only works if key == value |
How do you differentiate between 2 enums with same values? Eg type Sizes = 1 | 2; var x: Sizes = 2; var y: Colors = x; // Is there a way to make this fail? Although the enums share the same values, are semantically different things |
@rafayepes this should fail, because x is declared as |
What's the best way to export both types and values? [export] // @flow
export const TicketResult = {
OK: 'OK',
NG: 'NG',
EXPIRED: 'EXPIRED'
}
export type TicketResultType = $Keys<typeof TicketResult> [import] // @flow
import type {TicketResultType} from './ticket-result'
import {TicketResult} from './ticket-result'
function getTicketResult(): TicketResultType {
return TicketResult.OK
} Are there better ways? |
Also interested in the answer to the question above this comment. In particular in cases where my values are not the same as my enum keys. Ie. From example above |
I'm imagining something like |
@shinout Yeah that pretty much does it. @chrisui |
|
I have a noob question. I have a defs file with a bunch of enums basically. For example: export const LOAN_STATUS = {
PENDING: 'pending',
CURRENT: 'current',
DUE: 'due',
OVERDUE: 'overdue',
PENDING_PAYMENT: 'pending_payment',
CHARGED_OFF: 'charged_off',
VOIDED: 'voided',
DISPUTED: 'disputed',
REFUNDED: 'refunded',
SETTLED: 'settled',
} First question is why should I need to annotate this? Its static. Second is, if I annotate as type LoanStatusValues =
'pending' |
'current' |
'due' |
'overdue' |
'pending_payment' |
'charged_off' |
'voided' |
'disputed' |
'refunded' |
'settled'
type LoanStatusKeys =
'PENDING' |
'CURRENT' |
'DUE' |
'OVERDUE' |
'PENDING_PAYMENT' |
'CHARGED_OFF' |
'VOIDED' |
'DISPUTED' |
'REFUNDED' |
'SETTLED' Then I can use this type annotation export const ACTIVE_LOAN_STATUS : Array<LoanStatusValues> = [
LOAN_STATUS.OVERDUE,
LOAN_STATUS.CURRENT,
LOAN_STATUS.DUE,
LOAN_STATUS.PENDING_PAYMENT,
] Except its not any LoanStatusValues. So then I need this? type ActiveLoanStatus =
"current" |
"due" |
"overdue" |
"pending_payment"
export const ACTIVE_LOAN_STATUS : Array<ActiveLoanStatus> = [
LOAN_STATUS.OVERDUE,
LOAN_STATUS.CURRENT,
LOAN_STATUS.DUE,
LOAN_STATUS.PENDING_PAYMENT,
] I'm just getting started with Flow and I'm really excited about it, but this is really starting to feel like a pain in the butt. This is how I we're currently using these defs: if (defs.ACTIVE_LOAN_STATUS.indexOf(loan.status) !== -1) {
} I'd be happy to rewrite these enums with Flow, but then I cant actually use those definitions in JS: type ActiveLoanStatus =
"current" |
"due" |
"overdue" |
"pending_payment"
if (loan.status isTypeOf ActiveLoanStatus) {
} Anyways, I'd appreciate some help -- or perhaps I should put this on Stack Overflow? |
Yay for |
Just to put it down in writing on this thread, I think it'd be a mistake to implement the enum syntactic sugar for both types and values at this stage as there is talk of adding enums as an ES proposal and anything Flow adds should be based on those semantics. |
You're pretty much always going to be using an object literal to alias the actual values. Suppose you're working with an external api and don't get to choose the semantics. You might have something like this: const Status = {
chargedOff: 'charged-off',
} So type StatusType = $Values<Status> But I think it would be even more seamless if Flow weren't just type annotations but actually transpiled. That way you could defined an enum and its both a JS object and a union type. enum Status = {
chargedOff: 'charged-off',
} |
@thejameskyle is there any thread related to that ES proposal we could follow? Will it be that wrong to have something in flow before a proposal is stablished? Why not use this platform to experiment and drive the proposal, instead of waiting a proposal to be stablished and drive the users? Maybe flowtype implementation could be codemoded if diverges from the ES proposal, once that comes out? Do we really want to wait until this proposal reaches stage 4? Sorry for the rage of questions, but I'm very interested in the topic 😅 |
There's currently not proposal that's been made into a public repo or something. I don't think Flow is the right place to drive proposals either. We don't need to wait until it's stage 4 to feel confident implementing it, but right now it's stage -1 with the desire to implement from some tc39 members |
@thejameskyle Just to clarify, do you think Flow shouldn’t implement |
I don't use or know what you are referring to by `React.ElementConfig` so
it's obviously off topic. But I guess it's not even remotely similar to
basic enum functionality which you are suggesting that symbols like $ are
perfectly ok to be required. We're talking about generic typization syntax.
I don't see a response to the constants used as enumeration. Let's focus on
that.
…On 27 Jan 2018 18:07, "Andy Edwards" ***@***.***> wrote:
@antitoxic <https://github.com/antitoxic> and types like
React.ElementConfig aren't magical? You're just afraid of the $.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#627 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAP9fNj4j_PvjQwr4lUQUFP23jtoOmS1ks5tO0nRgaJpZM4FXpag>
.
|
Just wanted to confirm I understand what is left in this issue and maybe people who are slow to coming to this discussion can get up to speed (hope I don't seem like an idiot):
So this issue is essentially tracking support for literals correct? It sounds like @jedwards1211 pointed out some interesting cases where specific expressions may not work but IMO that's still pretty good. |
I'm in Flow 0.64, and it looks like const messageType = Object.freeze({
TEXT: 0,
CREATE_THREAD: 1,
});
type MessageType = $Values<typeof messageType>;
const test: MessageType = 2; // error Unfortunately, if you're using the constant as the key to a union-of-disjoint-shapes, there's no way to define the type of the union-of-disjoint-shapes without copy-pasting the values of your constant. type MessageInfo = {|
type: typeof messageType.TEXT,
text: string,
|} | {|
type: typeof messageType.CREATE_THREAD,
threadID: string,
|};
const test2: MessageInfo = { type: 1000, text: "hello" }; // no error Another annoyance is that if you want to have a runtime assertion for your enum that typechecks, you need to copy-paste all the values yet again: function assertMessageType(mType: number): MessageType {
invariant(
mType === messageType.TEXT || mType === messageType.CREATE_THREAD,
"number is not MessageType enum",
);
return mType; // error
} Looks like the value of |
Tried using Example const countries = Object.freeze({ IT: 'Italy', FR: 'France' });
type Country = $Values<typeof countries>;
const a: Country = 'hello' // error (this is correct) However this one is not working const obj = { IT: 'Italy', FR: 'France' };
const countries = Object.freeze(obj);
type Country = $Values<typeof countries>;
const a: Country = 'hello' // no error (this is wrong...) |
any news about this? |
Here are my 2 cents on How to create a enum module with a very few loc and also in a way that can be consumed consistently in flow and at runtime as well: colors.js (we use plural because it exports all the values as // Pay special attention to:
// 1. We are casting each value to its own, as otherwise all of them would be strings
// 2. Freezing would be strictly required if we weren't casting each value to its
// own (1), but as we are, its becomes optional here from the flow point of view
const all = Object.freeze({
green: ("COLOR_GREEN": "COLOR_GREEN"),
red: ("COLOR_RED": "COLOR_RED"),
yellow: ("COLOR_YELLOW": "COLOR_YELLOW"),
});
type Color = $Values<typeof all>;
// optional but sometimes useful too
// we are casting to any as Object.values returns Array<mixed> and mixed can't be casted
const values: Array<Color> = Object.freeze(Object.values(all): any);
export {all as defaut, values};
export type {Color}; otherModule.js import colors, {values as colorValues} from "./colors";
import type {Color} from "./color";
type SomethingRed = {
color: typeof colors.red
};
const thing: SomethingRed = {color: colors.red};
const color1: Color = colors.green;
const color2: Color = colors.red;
const color3: Color = colors.yellow;
console.log(colorValues); // ["COLOR_GREEN", "COLOR_RED", "COLOR_YELLOW"] If we weren't casting to their correspoding literal types (same value) and using Another alternative is to define one type for each value and then use an union to define the enum, but doing it this way is longer and also requires to define a type for Another good reason of using this alternative is that despite the fact that we need to write each value twice (the value itself and the casting to its literal type), it's done in the same expression which means that it's type checked, so in case we make a typo it will throw an error. I would like with all my ❤️ that someday flow add a better way to support this pattern, but meanwhile we can live with this way of declaring enums. |
I've ran in to the dreaded "flow/webstorm doesn't recognize flow's utility types" problem... Has anyone worked out a way to do this without using the utility types? I've found nothing out there on troubleshooting the error other than "use flow global instead", which didn't work, |
Would love to see a resolution to this issue. This is still the best I can muster without resorting to type Enum<T> = { [string] : T };
const RGB : Enum<'red' | 'green' | 'blue'> = {
RED: 'red',
GREEN: 'green',
BLUE: 'blue'
};
type RGB_ENUM = $Values<typeof RGB>
let color1 : RGB_ENUM = 'red';
let color2 : RGB_ENUM = 'gazorpazorp'; |
Hi @bluepnume, Be careful that the way you wrote doesn't fully type the values, as when you use Take a look to this comment where I described this more extensively. |
@mgtitimoli -- isn't that fine for most use cases, when the main thing I care about is that my variable contains one of the enumerated values? let color : RGB_ENUM = getColorFromSomewhere(); Now I can at least be sure that EDIT: aaah, I see what you're saying now I think. |
Exactly @bluepnume it works most of the times except for the case where you have a disjoint union where the tag is an enum (something very common), then in this case you will need to specify the exactly enum value and not the whole union, so having the possibility of doing the following would be super nice (types is the enum):
For the other cases I believe you are fine with the way you wrote. |
@andiwinata maybe that's worth a separate issue? Seems to me like a bug... |
@mgtitimoli Doesn't this work the same without the casting? Eg: const all = Object.freeze({
green: "COLOR_GREEN",
red: "COLOR_RED",
yellow: "COLOR_YELLOW",
});
type Color = $Values<typeof all>;
const purple: Color = "COLOR_PURPLE"; // error (correct) If you're getting the values from elsewhere (not an object literal), then this starts to fall apart. This may be related to the issue that @andiwinata mentioned: const colors = [
"COLOR_GREEN",
"COLOR_RED",
"COLOR_YELLOW"
];
const all = Object.freeze(colors.reduce((acc, curr) => {acc[curr] = curr; return acc;}, {}));
type Color = $Values<typeof all>;
const purple: Color = "COLOR_PURPLE"; // no error (incorrect) |
They do work in terms of the type @karlhorky, so for Another thing to have in mind is that it's pretty common to use enums to define the "tag" in disjoint unions, and for this case casting all the values to theirselves really help, because as I pointed before you are gonna be able to specify each disjoint union tag value directly with Regarding to what you wrote at the end of having all the values in an array, I believe it's related with the same thing, that is flow doesn't type any primitive value as the value itself (literal type) and instead the resulting type is the corresponding primitive (f.e.: |
Finally I found the probably best way to define enums... But you have to define your enum in a separate js file to make export opaque type Color: string = 'COLOR_GREEN' | 'COLOR_RED' | 'COLOR_YELLOW';
export const Colors = {
Green: ('COLOR_GREEN': Color),
Red: ('COLOR_RED': Color),
Yellow: ('COLOR_YELLOW': Color),
}; And any other js code: const green: Color = Colors.GREEN; // no error
const red: string = Colors.RED; // no error
const blue: Color = 'BLUE'; // error !!!
const yellow: Color = 'COLOR_YELLOW'; // error !!! -> because opaque types can only be created in the same file
const useColor = (color: Color) => { ... };
const readColor = (color: string) => { ... };
useColor(green); // no error
readColor(green); // no error
useColor(red); // error !!! -> because it was (down-)casted to string
readColor(red); // no error Is this finally the solution to the problem? |
That works @fdc-viktor-luft, but you are casting all your values to the union, so you are loosing the possibility to use each value independently. Mainly because of what I wrote above is why I ended up defining enums the way I described here. |
To be honest, I don't quite see your point @mgtitmoli. Using the example above. For me it is important that a Color can be used as string, but a somewhere else created string should never be usable as my enum Color. But if you do want the color to be not usable as string itself. You only have to change one line from above: export opaque type Color = 'COLOR_GREEN' | 'COLOR_RED' | 'COLOR_YELLOW'; Notice that I leaved the ": string" down-cast. Then a Color can not be used as string anymore and you would have to provide an additional toString-Function. |
Hi @fdc-viktor-luft, By doing this: export opaque type Color = 'COLOR_GREEN' | 'COLOR_RED' | 'COLOR_YELLOW';
export const Colors = {
Green: ('COLOR_GREEN': Color),
Red: ('COLOR_RED': Color),
Yellow: ('COLOR_YELLOW': Color),
}; And not this: const colors = {
green: ("COLOR_GREEN": "COLOR_GREEN"),
red: ("COLOR_RED": "COLOR_RED"),
yellow: ("COLOR_YELLOW": "COLOR_YELLOW"),
};
type Color = $Values<typeof colors>;
export default colors;
export type {Color}; You are telling flow that each enum value is the type of the enum and not of itself, so if you later would like to use a single value of the enum, you won't be possible. In the other hand, if you use the other way, you can get the type of each enum value independently. So, to summarize, The way you wrote: The other way: As a side note I would also recommend to name the enum module in plural, so you would import it: // @flow
import colors from "./colors";
import type {Color} from "./colors"; |
Yep, I've been using @mgtitimoli's approach for some time now and it works great. Really still hoping the Flow team can incorporate this as a first-class feature. It's very common for enum values to be different from the keys. |
@mgtitimoli and @bluepnume I've got your point why you are prefering the other solution, but in my opinion those are no real enums. If you look on any other programming language with enums you would never expect certain enum types as parameters of a function or anything else. Let's get some examples. import { Colors, type Color } from './path/to/colors.js';
const applyColorToStyle = (color: Color, style: Object) => ({ ...style, color });
const ColoredComponent = ({ color: Color }) => <div style={applyColorToStyle(color, {fontSize: '1.5em'}))}>Content</div>;
const SomeComponent = () => (
<>
<ColoredComponent color={Colors.RED} />
<ColoredComponent color={Colors.BLUE} />
</>
); In e.g. JAVA you have got something like: enum Color {
Green('COLOR_GREEN'),
Red('COLOR_RED'),
Yellow('COLOR_YELLOW');
private final String _value;
Color(final String value) { _value = value; }
public String getValue() { return _value; }
}
// And use it like so
public void printColorValue(final Color thatColor) { System.out.println(thatColor.getValue()); }
// I find no necessary use case for doing something like this
public void printRedColorValue(final RedColor thatColor) { System.out.println(thatColor.getValue()); }
// because I would rather do
public void printRedColorValue() { System.out.println(Color.RED.getValue()); } In your example it is still possible to create enum instances out of nowhere, like so: import type { Color } from "./colors";
const somethingRed: Color = 'COLOR_RED'; // without the usage of your colors export I know that with my solution something like your const doSomethingWithRed = (color: Color) => {
if (color === Colors.RED) run();
else if (color === Colors.GREEN) that();
else console.log('wrong color'); // like any other programming language
} Maybe instead of persisting that your solution is more like any enum, you could provide me a valid example of code, where it is necessary to have a |
One of the reasons |
For ex) function exhaustiveCheck(x: empty) {
throw new TypeError("Impossible case is detected");
}
function example(x): number {
switch (x) {
case Colors.RED:
return 5;
case Colors.GREEN:
return 4;
case Colors.YELLOW:
return 3;
default:
return exhaustiveCheck(x);
}
} will not give any error on |
I have to admit. The |
TypeScript has this
I'd prefer something that looked more like object literal syntax, for simpler translation to ES5:
It looks like you can get at this using disjoint unions:
So this may just be a request for a more standard syntax, or just a call out that this is how you do enumerations in FlowType.
The text was updated successfully, but these errors were encountered: