-
-
Notifications
You must be signed in to change notification settings - Fork 23
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
these are statements, not expressions #54
Comments
You're right! I guess i'm renaming this once again to try-statements :0 |
Why not let them be expressions, though? |
Their usage don't make sense. const result = try throw new Error()
// why not just:
const error = new Error() const result = try using x = something()
// This might make sense to catch a possible error
// of something() throwing before returning a resource
// but wouldn't handle any resource disposal thrown error. ... How would this resolve into a Result? const result = try {
statementOne();
statementTwo();
} the above is more related to const result = try do {
work()
} Since |
because the usage wouldn't ever be |
I think we are saying the same thing: Try is a statement that is followed by an expression. https://github.com/arthurfiorette/proposal-try-statements?tab=readme-ov-file#void-operations |
I'm suggesting that |
The main difference between a statement and an expression is that an expression represents a value that can be used in other expressions and statements, whereas a statement does not. A statement performs a side effect or imperatively alters the flow of execution and may return a value from the function scope (e.g., if, for, etc.). For example, try { } catch { } is a statement. If a proposal defines try as a statement, we cannot do the following: const x = try fn(); Conversely, if we can write this, then I also see no reason why try couldn't be inlined. I'd like to point that we cannot do something like that: const y = while(x < 9) x++; |
const error = try throw new Error('Some Error'); must be treated as a syntax error because throw is not an expression yet. Actually, according to the desugaring the let _result;
try {
_result = Result.ok(throw new Error('Some Error');
} catch(err) {
_result = Result.error(error);
}
const result = _result; But it is not allowed by JS, because |
It seems I got the initial concern to avoid inlining. @arthurfiorette , could you please correct me if I am wrong here. Let's consider the following case: const result = condition
? try doSomething()
: Result.ok(10); Current desugaring with So we need to consider replacing desugaring with IEFE, but it will require different desugarring for The example above could be desugarred to: const result = condition
? (() => { try { return Result.ok(doSomething()); } catch (error) { return Result.error(error); } })()
: Result.ok(10) Note: We need to use exactly arrow function expression to keep The async case: const result = condition
? try await doSomething()
: Result.ok(10); could be desugarred into: const result = condition
? await (() => {
try {
const _r = doSomething();
return isPromiseLike(_r) ? _r.then(Result.ok, Result.error) : Result.ok(_r);
} catch (error) {
return Result.error(error);
}
})()
: Result.ok(10); So, @ljharb, @arthurfiorette what do you think should we update desugaring approach? |
const result = condition
? try doSomething()
: Result.ok(10); would be more like: let _result;
if (condition) {
try {
_result = Result.ok(doSomething());
} catch (e) {
_result = Result.error(e);
}
} else {
_result = Result.ok(10);
}
const result = _result; there shouldn't need to be any new function scopes involved. |
Your approach does actually mean, that inlining is not possible, and each case of For example: const result = value || try doSomething(); // or the same with && const result = fn?.(try doSomething()); |
ah, true, there will definitely be some situations where my approach won't work. Either way tho, inlining is possible regardless - we're talking about the transpilation/desugaring, which isn't part of the proposal. |
Exactly ) By the way, what do you think about the following proposal: consider Result.error() as false value, to get able to write something like that: const result = (try doFirst()) || (try doSecond()); (Sorry, for offtop) |
It's not possible to have a falsy object. |
I know ) ... But we can just propose ))) Actually, we can consider something like Result static methods: {
const result = Result.firstOk(
try doSomething(),
try doSomethingElse(),
); // returns result of doSomething if it's ok is true, otherwise returns result of doSomethingElse
}
{
const result = Result.firstError(
try doSomething(),
try doSomethingElse(),
); // returns result of doSomething if it's ok is false, otherwise returns result of doSomethingElse
}
{
const results = Result.collect(
try doSomething(),
try doSomethingElse(),
); // returns Result that value is an array of successful results, and the first error met
} |
Proposing something that's a nonstarter would be counterproductive. |
But what about static methods? |
I think that the proposal would need to make a compelling case that there's a problem to solve, and that static methods are a good way to solve that problem. |
@arthurfiorette I don't understand your arguments here. Rule 1 of your proposal (can't be inlined) gives the following code snippet. I think the OP is asking to make this code snippet valid syntax, not the examples you listed above.
|
I understood it wrong and mistakenly renamed to statements. I was wrong and don't think limiting its usage is a good idea and it should behave just like
( try something())
(<operator> <expression>)
// above evaluates into a `Result` iterable object. This is all. Everything else is just the above concepts applied differently: const a = try something()
const [[ok, err, val] = [try something()]
const [ok, err, val] = try something()
array.map(fn => try fn())
yield try something()
try yield something()
try await something()
try (a instanceof b)
(try a) instanceof Result
const a = try try try try try try 1 |
I like this syntax: const [err, data] = try maybeThrow(); But with this rule:
By following this approach, we don't need an |
That’s unacceptable; being able to throw any value isn’t broken, and non-errors aren’t somehow “discouraged” to be thrown. |
The only way we could do that is if we ALWAYS wrap the error. So we'd have to created a new object with an If you want to save on the number of variables you have two options. let ok, error, tried;
[ok, error, tried] = try something();
if(!ok) {} // handle the error
const data = tried; // not sure how typing will work here.
//... repeat Or const result = try something();
if(!result.ok) { return console.error(result.error); } // handle the error
console.log(result.value) // use the value |
to be honest we have also another opportunity. const [failure, result] = try doSomething();
if (failure) { // if nothing has been thrown and caught `failure` will be undefined
console.log(`Error occurs: ${failure.error}`);
throw failure.error;
} So, it is something like to choose between more concise on the destructuring and risks to get issues with re-throwing, when the |
const result = try doSomething();
if (result.failure) {} Suffers from the same problem as if was destructured. Unless we partially define a field to be used like |
The way I see it from my limited understanding of the proposal process and my extensive experience using Javascript and Typescript, I think we only have two options that stand any chance of being accepted. Both of these can be strongly type-checked and give solid runtime guarantees with minimal code.
The other two options are the kinds of inconsistencies Javascript has been moving away from ever since ES5.
Thankfully I don't think any of you are endorsing the following, but for completeness, I present: The Worst Possible Option
|
also, instanceof doesn't work across realms, and is forgeable and breakable, so anything that encourages or requires an instanceof check isn't going to fly. |
Conditional wrapping is definitely bad. I just pointed out this kind of option to ensure we don’t lose anyone useful. |
Thanks @ljharb. Just renamed to https://github.com/arthurfiorette/proposal-try-operator?tab=readme-ov-file#try-operator I'll be closing this issue. |
Prerequisites
Versions
https://github.com/arthurfiorette/proposal-try-expressions?tab=readme-ov-file#rules-for-try-expressions item 1 says they can't be inlined, like return or throw - that makes them statements. If they were expressions, they'd be usable in any context expressions are.
A minimal reproducible example
n/a
Description
n/a
Steps to Reproduce
n/a
Expected Behavior
n/a
The text was updated successfully, but these errors were encountered: