Strawman: `Function.prototype.try` (or `Function.try`)

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

Strawman: `Function.prototype.try` (or `Function.try`)

Isiah Meadows-2
Usually, it's most convenient to just let errors easily fall through,
using `try`/`catch`/`finally`. But when you're writing more low-level
error-handling code, especially where errors are never exceptional
(like test errors in a testing framework or task errors in a
distributed worker pool implementation), it's often easier to handle
completions/results as if they were values, like how Lua [1] and Rust
[2] handle it.

[1]: https://www.lua.org/pil/8.4.html
[2]: https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html

Here's my proposal: introduce either `Function.try(func, thisArg,
...args)` or `Function.prototype.try(thisArg, ...args)`. It works much
like `Function.prototype.call`, except instead of returning/throwing,
it always returns a `{thrown, value}` pair.

Polyfilling either would be easy:

```js
Function.try = (func, thisArg, ...args) => {
    try {
        return {thrown: false, value: func.call(thisArg, ...args)}
    } catch (e) {
        return {thrown: true, value: e}
    }
}

Function.prototype.try = function (thisArg, ...args) {
    try {
        return {thrown: false, value: this.call(thisArg, ...args)}
    } catch (e) {
        return {thrown: true, value: e}
    }
}
```

Engines could make this much more performant, though, by detecting the
function, avoiding the object allocation if immediately destructured,
optimizing the arguments like `Function.prototype.call`, and if called
with a lambda, inlining the whole thing into a pseudo-`try`/`catch`.

-----

Isiah Meadows
[hidden email]

Looking for web consulting? Or a new website?
Send me an email and we can get started.
www.isiahmeadows.com
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Strawman: `Function.prototype.try` (or `Function.try`)

Jordan Harband
I think if we wanted to add a "Result" type, we might want to do that holistically instead of having an arbitrary prototype method return an arbitrary object literal.

It'd be really nice to have that type, but I'm not sure how easy it'd be to make a compelling case.

On Tue, Aug 22, 2017 at 10:01 AM, Isiah Meadows <[hidden email]> wrote:
Usually, it's most convenient to just let errors easily fall through,
using `try`/`catch`/`finally`. But when you're writing more low-level
error-handling code, especially where errors are never exceptional
(like test errors in a testing framework or task errors in a
distributed worker pool implementation), it's often easier to handle
completions/results as if they were values, like how Lua [1] and Rust
[2] handle it.

[1]: https://www.lua.org/pil/8.4.html
[2]: https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html

Here's my proposal: introduce either `Function.try(func, thisArg,
...args)` or `Function.prototype.try(thisArg, ...args)`. It works much
like `Function.prototype.call`, except instead of returning/throwing,
it always returns a `{thrown, value}` pair.

Polyfilling either would be easy:

```js
Function.try = (func, thisArg, ...args) => {
    try {
        return {thrown: false, value: func.call(thisArg, ...args)}
    } catch (e) {
        return {thrown: true, value: e}
    }
}

Function.prototype.try = function (thisArg, ...args) {
    try {
        return {thrown: false, value: this.call(thisArg, ...args)}
    } catch (e) {
        return {thrown: true, value: e}
    }
}
```

Engines could make this much more performant, though, by detecting the
function, avoiding the object allocation if immediately destructured,
optimizing the arguments like `Function.prototype.call`, and if called
with a lambda, inlining the whole thing into a pseudo-`try`/`catch`.

-----

Isiah Meadows
[hidden email]

Looking for web consulting? Or a new website?
Send me an email and we can get started.
www.isiahmeadows.com
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Strawman: `Function.prototype.try` (or `Function.try`)

Oriol _
In reply to this post by Isiah Meadows-2
Instead of `thrown: false` or `thrown: false`, why not `type: "normal"`, `type: "return"` or `type: "throw"`? This is what completion records use.

-Oriol

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Strawman: `Function.prototype.try` (or `Function.try`)

Sebastian Malton
1. What is the difference between "normal" and "return"?

2. As previously stated a new type would probably be best since this can be made much better then what is essentially a try/catch.
Since try catching is not very efficient it would be better to have some sort of type. However, this leads to how to implement, it could just be a class but then it could just be added manually. The real benefit to this would be great integration with async/await and pattern matching 

Sent: August 22, 2017 6:23 PM
Subject: Re: Strawman: `Function.prototype.try` (or `Function.try`)

Instead of `thrown: false` or `thrown: false`, why not `type: "normal"`, `type: "return"` or `type: "throw"`? This is what completion records use.

-Oriol

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Re: Strawman: `Function.prototype.try` (or `Function.try`)

Dante Federici
In reply to this post by Isiah Meadows-2
There exist lots of libraries that give this sort of functionality among others in the functional community -- lots of Either or Maybe implementations.

I don't think this is a healthy addition to the spec since you would then want to consider all the other monadic goodies like mapping over these values, folding, "all" and "some", etc.

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Strawman: `Function.prototype.try` (or `Function.try`)

kai zhu

There exist lots of libraries that give this sort of functionality among others in the functional community -- lots of Either or Maybe implementations.

I don't think this is a healthy addition to the spec since you would then want to consider all the other monadic goodies like mapping over these values, folding, "all" and "some", etc.

i agree.  a 10-line vanilla es5 solution already exists for what i think are the expected use-cases.






use tryCatchOnError to either
a) try to return the decrypted jwt token, or
b) return an empty object if an error was thrown during decryption



use tryCatchOnError in an asynchronous ajax function



use tryCatchOnError to test for exceptions



On Aug 23, 2017, at 1:01 AM, Isiah Meadows <[hidden email]> wrote:

Usually, it's most convenient to just let errors easily fall through,
using `try`/`catch`/`finally`. But when you're writing more low-level
error-handling code, especially where errors are never exceptional
(like test errors in a testing framework or task errors in a
distributed worker pool implementation), it's often easier to handle
completions/results as if they were values, like how Lua [1] and Rust
[2] handle it.

[1]: https://www.lua.org/pil/8.4.html
[2]: https://doc.rust-lang.org/book/second-edition/ch09-02-recoverable-errors-with-result.html

Here's my proposal: introduce either `Function.try(func, thisArg,
...args)` or `Function.prototype.try(thisArg, ...args)`. It works much
like `Function.prototype.call`, except instead of returning/throwing,
it always returns a `{thrown, value}` pair.

Polyfilling either would be easy:

```js
Function.try = (func, thisArg, ...args) => {
   try {
       return {thrown: false, value: func.call(thisArg, ...args)}
   } catch (e) {
       return {thrown: true, value: e}
   }
}

Function.prototype.try = function (thisArg, ...args) {
   try {
       return {thrown: false, value: this.call(thisArg, ...args)}
   } catch (e) {
       return {thrown: true, value: e}
   }
}
```

Engines could make this much more performant, though, by detecting the
function, avoiding the object allocation if immediately destructured,
optimizing the arguments like `Function.prototype.call`, and if called
with a lambda, inlining the whole thing into a pseudo-`try`/`catch`.

-----

Isiah Meadows
[hidden email]

Looking for web consulting? Or a new website?
Send me an email and we can get started.
www.isiahmeadows.com
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Strawman: `Function.prototype.try` (or `Function.try`)

Mike Samuel
In reply to this post by Sebastian Malton


On Tue, Aug 22, 2017 at 2:08 PM, Sebastian Malton <[hidden email]> wrote:
1. What is the difference between "normal" and "return"?

Noone's answered yet, so I'll take a stab.

An instruction that completes normally transfers control to the "next" instruction analogous to incrementing the program counter.

An instruction that returns pops (modulo yield) a stackframe which includes resetting the program counter to its state prior to the call, unless there are protected regions containing the program counter in which case it jumps to the innermost.  Protected regions in EcmaScript correspond to try{...} for the most part.

 
2. As previously stated a new type would probably be best since this can be made much better then what is essentially a try/catch.
Since try catching is not very efficient it would be better to have some sort of type. However, this leads to how to implement, it could just be a class but then it could just be added manually. The real benefit to this would be great integration with async/await and pattern matching 

Others are much more familiar with VM performance constraints, but IIRC, its not the catching that is inefficient, but associating the stackframe with the exception.
Dispatching an exception within a stack frame just involves finding the innermost protected region containing the PC which is a binary search over a fairly small list of instruction indices.
It sometimes matters whether you associate stack information with an Error on `new Error()` as in Java, or on throw as in Python but IIRC EcmaScript doesn't yet take a position on that.
Throwing a non-Error-value also needn't incur the stack walk.

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Strawman: `Function.prototype.try` (or `Function.try`)

Isiah Meadows-2
@all

BTW, I specifically avoided `type: "normal"`, etc., because only
`"normal"` and `"throw"` are exposed to JS in any significant capacity
- they're what you can detect using `try`/`catch`. The others are
internal-only exception-like values used in the spec to implement
breaking and loop continuation in its internal AST interpreter.
Although I said completions, I meant it loosely.

(All browser engines instead operate on control flow graphs to
generate bytecode, so they also only really use `"normal"` and
`"throw"`.)

In addition, monads are a great way of introducing complexity into the
language, and absent pattern matching *at all* (or anything else
similarly extensible), let's save that rabbit hole for another time.

Here's some more specific responses inline:

On Tue, Aug 22, 2017 at 1:21 PM, Jordan Harband <[hidden email]> wrote:
> I think if we wanted to add a "Result" type, we might want to do that
> holistically instead of having an arbitrary prototype method return an
> arbitrary object literal.
>
> It'd be really nice to have that type, but I'm not sure how easy it'd be to
> make a compelling case.

I like this, but the complicated monadic whatnot that was
*immediately* suggested is quite frankly *not* the ideal way to handle
this, since we already have standard exception handling.

What about something like this, just to start? Of course, there's
probably big things missing here, but just thought I'd get the ball
rolling a little.

```js
class Result<T> {
    constructor(init: () => T);
    value: any;
    type: "return" | "throw";
    get(): T; // throws if `init` threw
}
```

(I'd rather start small than wind up with something over-engineered
and really bad.)

On Tue, Aug 22, 2017 at 2:08 PM, Sebastian Malton <[hidden email]>
> wrote:
>
> 1. What is the difference between "normal" and "return"?

You get `"return"` when returning from a function. The spec implements
early returns using this (a special exception-like completion), since
it just interpret's the AST directly.

Engines don't actually use this method, though. They build a control
flow graph, and they lower `let`s into something not unlike `var`s, so
they don't actually need to have anything like a special exception to
handle these cases.

> Since try catching is not very efficient it would be better to have some sort of type.

V8 has been the exception, not the rule (no pun intended :-)) in
having very poor exception handling performance. Every other engine
has optimized them very well at this point. (Spidermonkey used to have
issues with functions that *threw* exceptions, but that's not as much
of a problem as it used to be, and it only deopt'ed once it actually
threw.)

In addition, V8's new interpreter/compiler pipeline, Ignition and
TurboFan, no longer has this issue, and can fully optimize code with
`try`/`catch` in it.

On Tue, Aug 22, 2017 at 3:46 PM, Mike Samuel <[hidden email]> wrote:

>
>
> Noone's answered yet, so I'll take a stab.
>
> An instruction that completes normally transfers control to the "next"
> instruction analogous to incrementing the program counter.
>
> An instruction that returns pops (modulo yield) a stackframe which includes
> resetting the program counter to its state prior to the call, unless there
> are protected regions containing the program counter in which case it jumps
> to the innermost.  Protected regions in EcmaScript correspond to try{...}
> for the most part.
>



>
>>
>> 2. As previously stated a new type would probably be best since this can
>> be made much better then what is essentially a try/catch.
>> Since try catching is not very efficient it would be better to have some
>> sort of type. However, this leads to how to implement, it could just be a
>> class but then it could just be added manually. The real benefit to this
>> would be great integration with async/await and pattern matching
>
>
> Others are much more familiar with VM performance constraints, but IIRC, its
> not the catching that is inefficient, but associating the stackframe with
> the exception.
> Dispatching an exception within a stack frame just involves finding the
> innermost protected region containing the PC which is a binary search over a
> fairly small list of instruction indices.
> It sometimes matters whether you associate stack information with an Error
> on `new Error()` as in Java, or on throw as in Python but IIRC EcmaScript
> doesn't yet take a position on that.
> Throwing a non-Error-value also needn't incur the stack walk.
>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
>

-----

Isiah Meadows
[hidden email]

Looking for web consulting? Or a new website?
Send me an email and we can get started.
www.isiahmeadows.com
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss