-
-
Notifications
You must be signed in to change notification settings - Fork 511
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
Allow use of non-null assertion on Eithers #1343
Comments
I support your desired behaviour. Anyway, I think your suggested solution (adding an optional value of While the model should not be changed (it describes the nature of // return-type of `Right<A>` instead of a generic `Either<never, A>`
export const right = <A>(a: A): Right<A> => ({ _tag: 'Right', right: a })
// return-type of `Left<E>` instead of a generic `Either<E, never>`
export const left = <E>(e: E): Left<E> => ({ _tag: 'Left', left: e }) I don't know what could be the reasons it wasn't implemented this way (it may be because of historic TypeScript limitations). |
Since typescript uses structural typing, this is already the case, and in fact it's worse right now than it would be with this change: const doSomethingWithEither = <L, R>(e: Either<L, R>) => ...
const myEither = {
_tag: 'Right',
right: 123,
left: 456,
} as const;
doSomethingWithEither(myEither) Right now, this compiles without errors. With this change, the compiler would catch the mistake. And an optional property with type undefined is technically accurate. The |
That's not how ADTs work and could encourage bad habits. The model of Think about the 99.99% (I would even say 100%) of use cases where developers will look at this Your suggested solution is a kind of hack. I believe there are better ways to achieve the desired behaviour, and if not, then it's ok to be satisfied with the awkwardness of testing the |
I'm curious what bad habits you envisage from this. I'd argue that the added inconvenience of having to use type-guarded I'm ready to accept the point that it's unusual, and therefore potentially confusing. I'm just struggling to picture a case where the answer to "Should they worry about it?" Or "Should they test or verify it?" would lead to a bad outcome. Checking against
I agree. Right now, the model tells "nothing but the truth". I want something closer to "the whole truth". In typescript, all conforming to a shape In fact, TypeScript has the export interface Left<E> {
readonly _tag: 'Left'
readonly left: E
readonly right?: never
}
export interface Right<A> {
readonly _tag: 'Right'
readonly right: A
readonly left?: never
} |
For "the whole truth" we would need to define all possible attributes that are not allowed to be on the Besides that you make the DX worse cause now
Taking shortcuts in programming leads always to bad code quality but it does not harm the correctness of your program even when the developer adds attributes that do not belong to this type. |
That's why I used the word "closer". Most attributes are completely irrelevant and provide no value. Not sure why you're bringing them up.
I don't follow. I think the DX would be a lot better. Users are usually dealing with
|
Anyway, it seems I'm in the minority here. I'll leave open in case @gcanti happens to agree with me! |
@mmkal I think you should read the answers again, slowly and carefully, because what you want is simply based on aesthetic feelings. Remember, the design is not how things look but how things work. It's only a barrier in your mind. |
Thanks for the sage advice, @steida. I read every comment carefully, and I believe I understand them. I also responded to them - if you feel I haven't, feel free to reply to what I actually wrote. You are right, though, that this is a feature request based on aesthetics. Code is written for humans. Library design should consider both aesthetics and functionality. It shouldn't sacrifice correctness for aesthetics, but I believe in this case it doesn't. I maintain that my suggestion is an accurate model of the data structure, and the aesthetics are better. This request is based on using fp-ts in the real world, where there are negative side-effects of bad aesthetics, because of human imperfection. Human beings have feelings and care about aesthetics, so when the design forces them to write ugly code, the chances that they'll sidestep the ugliness in a dangerous way increase - especially in a language like typescript with To repeat what I said two weeks ago:
Assuming that's not the case, @gcanti feel free to close this issue - it's a pretty low-quality discussion at this point. |
I want to add that you could also cast const myEither = E.right(123) as E.Right<number>
const num1 = myEither.right // works
const dummy = <E, T>(x: E.Either<E, T>) => x
const v = dummy(myEither) // works, but turned into E.Either<unknown, number>
const dummy2 = <E=never, T=never>(x: E.Either<E, T>) => x
const v2 = dummy2(myEither) // works, v2 is still E.Either<never, number> |
I'm not sure you meant it this way, but honestly that's exactly the sort of thing I'm worried about. const myEither = E.left(123) as E.Right<number>
const num1 = myEither.right.toString() // compiles without errors, but really shouldn't Also, you're duplicating the type from the value in the cast. What if it's more complex than const myEither = E.right({
foo: {
bar: {
baz: [123]
}
}
}) as E.Right<{
foo: {
bar: {
baz: number[]
}
}
}>
const num1 = myEither.right.foo.bar.baz[0] To solve specifically for |
I tracked down the discussion for #823 You are right that As an alternative non production environment, you could define and use assertion like bellow: function assertRight<E, A>(e: E.Either<E, A>): asserts e is E.Right<A> {
if (E.isLeft(e)) throw new Error('Value must be right')
}
const myEither = E.right(123)
assertRight(myEither)
const num1 = myEither.right |
I want to show you an example of ADT that make sense. const absurd = (a: never) => {}
type Rectangle = {tag: "Rectangle", width: number, height: number }
type Circle = {tag: "Circle", radius: number }
type Square = {tag: "Square", side: number }
type Shape = Rectangle | Circle | Square
function area(shape: Shape) {
switch(shape.type) {
case "Rectangle":
return shape.width * shape.height;
case "Circle":
return shape.raidus ** 2 * Math.PI;
case "Square":
return shape.side ** 2;
}
absurd(shape);
} in this example, typescript is helping you out in each case you know exactly what properties are available to you. let's say after 2 months you want to add Ellipse to your shape, do you add all it's properties to other shapes? even in object oriented programming on inhtertance you don't add all properites of subclass to its parent. |
@mohaalak Offtopic, but I couldn't help myself: if you add the return type to |
🚀 Feature request
Current Behavior
Desired Behavior
Suggested Solution
Update the
Left
andRight
interfaces to:By adding the optional properties with type
undefined
, we've kept the type system accurate - and in fact more informative. We know that anEither
must not have the "other" property. But we've allowed non-null assertions in code where we're sure that the the value is a right (or left, as the case may be).Who does this impact? Who is this for?
Specifically, I'm thinking about test code, or temporary debugging to reveal types in IDEs. In most production cases, it's better to use
pipe
, orisLeft
/isRight
, or check that_tag
property. But that's an unnecessary overhead in lots of non-production cases, and this would be very convenient without introducing any danger.To protect against abuse, there's the no-non-null-assertion eslint rule - which is nice because it can be applied selectively. For example, only to production code, and the rule can be relaxed in
*.test.ts
files, or similar.Describe alternatives you've considered
Checking
._tag
works, but it's awkward sometimes.Your environment
The text was updated successfully, but these errors were encountered: