LiveScript icon indicating copy to clipboard operation
LiveScript copied to clipboard

`try/else` proposal

Open ghost opened this issue 10 years ago • 10 comments

Think of some procedure to search for config file in couple of locations, in order. It would be something like this:

find-config = (...locations) ->
    for filename in locations
        try
            return new Config fs.read-file-sync filename, encoding: \utf8

    throw new Error "Could not find configuration file"

And to be used like this:

find-config do
    "${process.env['HOME']}/.foobarrc"
    './etc/foobar.conf'
    '/etc/foobar.conf'

But if we add a new requirement for config to fail on incorrect config files, so new Config can now throw if could not parse, code becomes uglier:

find-config = (...locations) ->
    for filename in locations
        try
            data = fs.read-file-sync filename, encoding: \utf8
        catch
            continue

        try
            return new Config data
        catch
            throw new Error "Malformed configuration file #{filename}: #{e}"

    throw new Error "Could not find configuration file"

If there was an else construct, it could be written like this:

find-config = (...locations) ->
    for filename in locations
        try
            data = fs.read-file-sync filename, encoding: \utf8
        else
            try
                return new Config data
            catch
                throw new Error "Malformed configuration file #{filename}: #{e}"

    throw new Error "Could not find configuration file"

Which results in more compact and readable code, and works much better when there is no option to continue a loop. So, I propose to add optional else branch to try/catch/else/finally construct, which should execute when exception didn't happen, instead of catch clause and before finally.

ghost avatar Jun 30 '15 12:06 ghost

I am the OP, sorry for using wrong account, forgot to return to the one which is mine.

ruby-random avatar Jun 30 '15 12:06 ruby-random

So, following code:

try
    TRY-BODY
catch EXC-EXPR
    CATCH-BODY
else
    ELSE-BODY
finally
    FINALLY-BODY

Should compile to:

// these are hoisted to the top of scope, and are common for all `try` within scope
var canary$ = new Object();
var ec$;
var EXC-EXPR;

// on each `try`
ec$ = canary$;
try {
    TRY-BODY
} catch (e$) {
    EXC-EXPR = ec$ = e$;
    CATCH-BODY
} finally {
    if (ec$ === canary$) {
        ELSE-BODY
    }
    FINALLY-BODY
}

Separate ec$ variable is required so EXC-EXPR can be deconstructing assignment or anything at all, like it is now. canary$ is unique and can't be thrown (unlike null or undefined), so it can be checked against for whether it had changed or not.

ruby-random avatar Jun 30 '15 13:06 ruby-random

canary$ and ec$ only appear if there is else clause. When there is no else, there is no need in those, and JS output should be the same as in current version.

ruby-random avatar Jun 30 '15 13:06 ruby-random

Or, as @vendethiel suggested, just assign thrown$ = true; inside catch clause:

// these are hoisted to the top of scope, and are common for all `try` within scope
var thrown$;
var EXC-EXPR;

// on each `try`
thrown$ = false;
try {
    TRY-BODY
} catch (e$) {
    thrown$ = true;
    EXC-EXPR = e$;
    CATCH-BODY
} finally {
    if (!thrown$) {
        ELSE-BODY
    }
    FINALLY-BODY
}

ruby-random avatar Jun 30 '15 14:06 ruby-random

I find this a little confusing to read (even though I understand what you're talking about). else reads too similarly semantically to catch in exception handling, especially coming from a functional programming background. Also, IMHO it would be better to be more explicit here.

And another thing to take a look at: recent discussion to remove some of the unnecessary sugar. For what it's worth, that alone has increased skepticism for new features.

dead-claudia avatar Jul 07 '15 03:07 dead-claudia

I'm not sure if it matters, but I +1 this idea and prefer the thrown$ = true method over the use of comparing against an Object.

kkirby avatar Jul 08 '15 15:07 kkirby

@impinball try/else syntax is pretty straightforward for Python users, as Python provides else clause for most flow control constructs (if, try, while, for etc.) to indicate "successful, non-breaked, non-errored completion". However, as to my knowledge, only if/else and try/else are widespread.

ruby-random avatar Jul 08 '15 15:07 ruby-random

@kkirby yes, thrown$ is the way to go. I don't know what was in my mind when I suggested sentinel/canary object approach, maybe I just forgot that new variables can be introduced :)

ruby-random avatar Jul 08 '15 15:07 ruby-random

We have for/else, and I prefer our semantics to python's. :)

vendethiel avatar Jul 08 '15 16:07 vendethiel

I can't decide whether I Iike LS's for .. else or python's. Both are good, for their own usecases. If python used the keyword nobreak instead, I would have liked a combination of both. for .. nobreak .. else would be super awesome.

phanimahesh avatar Jul 09 '15 10:07 phanimahesh