Thread overview
TryElseExpression DIP
Sep 05, 2016
pineapple
Sep 05, 2016
ag0aep6g
Sep 05, 2016
pineapple
Sep 05, 2016
Jacob Carlborg
Sep 05, 2016
pineapple
Sep 05, 2016
Basile B.
Sep 05, 2016
arturg
September 05, 2016
I was writing some code and realized D doesn't have an equivalent to Python's `else` for error handling, and I think we should change that

https://github.com/dlang/DIPs/pull/43/files



In Python, the try/catch/finally syntax is augmented with an additional clause,
termed else. It is a fantastically useful addition to the conventional syntax.
It works like this:

    try:
        do_something()
    except Exception as e:
        pass # Runs when an error inheriting from Exception was raised
    else:
        pass # Runs when no error was raised
    finally:
        pass # Runs unconditionally, evaluates last

Imitating this functionality in D,

    try{
        do_a_thing();
    }catch(Exception exception){
        handle_error();
    }else{
        depends_on_success_of_thing();
    }finally{
        do_this_always();
    }

Would be equivalent to

    bool success = false;
    try{
        do_a_thing();
        success = true;
    }catch(Exception exception){
        handle_error();
    }finally{
        try{
            if(success){
                depends_on_success_of_thing();
            }
        }finally{
            do_this_always();
        }
    }


September 05, 2016
On 09/05/2016 08:07 PM, pineapple wrote:
>     try{
>         do_a_thing();
>     }catch(Exception exception){
>         handle_error();
>     }else{
>         depends_on_success_of_thing();
>     }finally{
>         do_this_always();
>     }
>
> Would be equivalent to
>
>     bool success = false;
>     try{
>         do_a_thing();
>         success = true;
>     }catch(Exception exception){
>         handle_error();
>     }finally{
>         try{
>             if(success){
>                 depends_on_success_of_thing();
>             }
>         }finally{
>             do_this_always();
>         }
>     }

Can you point out how this is different from (and better than)

    try { do_a_thing(); depends_on_success_of_thing(); }
    catch (...) { ... }
    finally { ... }

?
September 05, 2016
On Monday, 5 September 2016 at 18:07:52 UTC, pineapple wrote:

> It works like this:
>
>     try:
>         do_something()
>     except Exception as e:
>         pass # Runs when an error inheriting from Exception was raised
>     else:
>         pass # Runs when no error was raised
>     finally:
>         pass # Runs unconditionally, evaluates last
>
> Imitating this functionality in D,
>
>     try{
>         do_a_thing();
>     }catch(Exception exception){
>         handle_error();
>     }else{
>         depends_on_success_of_thing();
>     }finally{
>         do_this_always();
>     }
>
> Would be equivalent to
>
>     bool success = false;
>     try{
>         do_a_thing();
>         success = true;
>     }catch(Exception exception){
>         handle_error();
>     }finally{
>         try{
>             if(success){
>                 depends_on_success_of_thing();
>             }
>         }finally{
>             do_this_always();
>         }
>     }

hm, isn't this similar/same as the python version?

  try{
      doSomething();

      scope(success) "if no Exception thrown".writeln;
  }
  catch(Exception e)
  {
       ...
  }

September 05, 2016
On Monday, 5 September 2016 at 18:27:44 UTC, ag0aep6g wrote:
> Can you point out how this is different from (and better than)
>
>     try { do_a_thing(); depends_on_success_of_thing(); }
>     catch (...) { ... }
>     finally { ... }
>
> ?

On Monday, 5 September 2016 at 18:27:52 UTC, arturg wrote:
> hm, isn't this similar/same as the python version?
>
>   try{
>       doSomething();
>
>       scope(success) "if no Exception thrown".writeln;
>   }
>   catch(Exception e)
>   {
>        ...
>   }

In this case, the catch block will catch both errors from do_a_thing and depends_on_success_of_thing. The places where this sort of pattern is most useful are where you don't want to handle errors in depends_on_success_of_thing at all, because to have thrown an error is very unexpected behavior.

Granted scenarios like that are uncommon, but the most important reason for using the separate `else` scope is to help reduce programmer error because they forgot that the error handling in the catch block will break something when it was entered because depends_on_success_of_thing failed.

There's also the matter of readability and cleanliness. It is (in my opinion, at least) an intuitive and concise way to handle a not-uncommon case.


September 05, 2016
On 2016-09-05 20:57, pineapple wrote:

> In this case, the catch block will catch both errors from do_a_thing and
> depends_on_success_of_thing.

Then move it to after the "finally" block.

-- 
/Jacob Carlborg
September 05, 2016
On Monday, 5 September 2016 at 19:12:02 UTC, Jacob Carlborg wrote:
> On 2016-09-05 20:57, pineapple wrote:
>
>> In this case, the catch block will catch both errors from do_a_thing and
>> depends_on_success_of_thing.
>
> Then move it to after the "finally" block.

It would actually have to be inside the "finally" block to reproduce the exact behavior. I included an analog in the original post - I'm not saying that this behavior can't be achieved now, but that this is a concise and more readable pattern which, when utilized, makes it more difficult to accidentally write error-prone code.

Which is easier to read and to write? Which is more maintainable? Which is less prone to programmer errors? This?


    bool success = false;
    try{
        do_a_thing();
        success = true;
    }catch(Exception exception){
        handle_error();
    }finally{
        try{
            if(success){
                depends_on_success_of_thing();
            }
        }finally{
            do_this_always();
        }
    }

Or this?

    try{
        do_a_thing();
    }catch(Exception exception){
        handle_error();
    }else{
        depends_on_success_of_thing();
    }finally{
        do_this_always();
    }



This?

    try{
        do_a_thing();
    }else{
        depends_on_success_of_thing();
    }

Or this?

    bool success = false;
    try{
        do_a_thing();
        success = true;
    }finally{
        if(success){
            depends_on_success_of_thing();
        }
    }
September 05, 2016
On Monday, 5 September 2016 at 20:04:43 UTC, pineapple wrote:
> On Monday, 5 September 2016 at 19:12:02 UTC, Jacob Carlborg wrote:
> [...]
>> On 2016-09-05 20:57, pineapple wrote:
> Which is easier to read and to write? Which is more maintainable? Which is less prone to programmer errors? This?
>
> [...]
> This?
>
>     try{
>         do_a_thing();
>     }else{
>         depends_on_success_of_thing();
>     }
>
> Or this?
>
>     bool success = false;
>     try{
>         do_a_thing();
>         success = true;
>     }finally{
>         if(success){
>             depends_on_success_of_thing();
>         }
>     }

There's probably also a FP approach:

function TryElse(
    alias TryStatements,
    alias ElseStatements)(){}

function TryCatchElseFinally(
    alias TryStatements,
    alias ElseStatements,
    alias CatchStatements,
    alias ElseStatements,
    E : Exception = Exception)(){}

etc.

You pass delegate literals:

°°°°°°°°°°°°°°°°°°°°°
void TryElse(alias TryStatements, alias ElseStatements)()
{
    bool doElses = true;
    try TryStatements();
    catch(Throwable){}
    ElseStatements();
}

void main()
{
    int i;
    import std.conv: to;

    TryElse!(
        {i = to!int("0.42");},
        {i = -1;}
    );
}
°°°°°°°°°°°°°°°°°°°°°

With optional sugar to name each statements group:

°°°°°°°°°°°°°°°°°°°°°
template Statements(alias T)
{
    alias Statements = T;
}
alias Try = Statements;
alias Else = Statements;

void main()
{
    TryElse!(
        Try!({}),
        Else!({})
    );
}
°°°°°°°°°°°°°°°°°°°°°