Nested Case Statements v.s. multiple functions

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
24 messages Options
12
Reply | Threaded
Open this post in threaded view
|

Nested Case Statements v.s. multiple functions

asdf asdf
Hello everyone,

As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:

Send_update(Arg1) ->
        case do this(Arg1) of
                {ok, [Val1, Val2]} ->  
                        case do_that(Val1, Val2) of
                                {ok, [Val3, Val4]} ->
                                        case do_this2(…) of
….

It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.

So I went and I converted it to a top level function that would then call lower level functions like so:
                       


 send_update(Arg1) ->  
     case ... of
         {ok, [Val1, Val2]} ->  
             send_update(check_indices, {Arg1, Val1, Val2});
         Else ->  
             lager:error("ERROR: ..")
     end.
 send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
 send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of  
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error ->  
             lager:error("ERROR: …")
     end;
 send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.


Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.

Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.

Thank you for your advice!
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Daniel Goertzen-3
You are headed in the right direction.  Two comments on your refactor:

1. Ditch the atom selector.  Just make this part of the function name.
2. You can flatten out the remaining case expressions if you like.  Whether this is better or not is a matter of opinion, but it definitely makes tracing easier.

This is where I got with your code...

send_update(Arg1) ->
    send_update_check_indices(...).

send_update_check_indices({ok, [Val1, Val2]}) ->
    send_update_check_values(...);
send_update_check_indices(Else) ->
    lager:error("ERROR: ..").

% and so on



On Mon, Sep 25, 2017 at 8:26 AM code wiget <[hidden email]> wrote:
Hello everyone,

As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:

Send_update(Arg1) ->
        case do this(Arg1) of
                {ok, [Val1, Val2]} ->
                        case do_that(Val1, Val2) of
                                {ok, [Val3, Val4]} ->
                                        case do_this2(…) of
….

It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.

So I went and I converted it to a top level function that would then call lower level functions like so:



 send_update(Arg1) ->
     case ... of
         {ok, [Val1, Val2]} ->
             send_update(check_indices, {Arg1, Val1, Val2});
         Else ->
             lager:error("ERROR: ..")
     end.
 send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
 send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error ->
             lager:error("ERROR: …")
     end;
 send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.


Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.

Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.

Thank you for your advice!
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Nathaniel Waisbrot
In reply to this post by asdf asdf
More functions is almost always better. You are writing in a functional language. :) I lean towards naming the functions for what they'll do, rather than having an initial arg that tells them what to do: `do_something(Arg)` rather than `do(something, Arg)`.

In your code, it looks like you have a success-path and anything that deviates is an error (a serious one?). I'd begin by writing it:

```
send_update(Request) ->
    {ok, IndexArgs} = check_request(Request),
    {ok, IndexResult} = check_indices(IndexArgs)
    {ok, ValueResult} = get_values(IndexResult)
    ok = transmit(ValueResult).
```

Here, we have a clear description of the happy-path and anything else will crash. If this is a server handling single-shot client requests (a REST server, for example) then you can either silently drop bad clients or have a top-level exception handler that just returns a 4xx or 5xx error. That may be enough right there, but even if you want richer responses to your clients I think the happy-path is a great way to start. The other thing it will do is help you start to think about overall resilience: it's cool if you drop one TCP connection because of an unexpected request, but all your other live connections and the rest of the system ought to be unaffected.



> On Sep 25, 2017, at 9:25 AM, code wiget <[hidden email]> wrote:
>
> Hello everyone,
>
> As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
>
> Send_update(Arg1) ->
> case do this(Arg1) of
> {ok, [Val1, Val2]} ->  
> case do_that(Val1, Val2) of
> {ok, [Val3, Val4]} ->
> case do_this2(…) of
> ….
>
> It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
>
> So I went and I converted it to a top level function that would then call lower level functions like so:
>
>
>
> send_update(Arg1) ->  
>     case ... of
>         {ok, [Val1, Val2]} ->  
>             send_update(check_indices, {Arg1, Val1, Val2});
>         Else ->  
>             lager:error("ERROR: ..")
>     end.
> send_update(check_indices, {Arg1, Arg2, Arg3}) ->
>     case check_indices(Arg2, Arg3)of
>         true ->
>             send_update(get_values, {Arg1, Arg3});
>         false ->
>             lager:error("EMERGENCY: ….")
>     end;
> send_update(get_values, {Arg1, Arg2}) ->
>   ...
>     case ... of  
>         {ok, [Val1, Val2, VAl3]} ->
>             send_update(send_value, {Arg1, Val1, Val2, Val3});
>         Error ->  
>             lager:error("ERROR: …")
>     end;
> send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
>    …
>     Do_something(Args),
>     ok.
>
>
> Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
>
> Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
>
> Thank you for your advice!
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

zxq9-2
In reply to this post by asdf asdf
On 2017年09月25日 月曜日 09:25:48 code wiget wrote:

> Hello everyone,
>
> As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
>
> Send_update(Arg1) ->
> case do this(Arg1) of
> {ok, [Val1, Val2]} ->  
> case do_that(Val1, Val2) of
> {ok, [Val3, Val4]} ->
> case do_this2(…) of
> ….
>
> It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.

This happens a lot when you're first starting out. Don't worry, it gets better.

And you're headed in the right direction, asking the right questions, clearly sensitive to the right smells.

> So I went and I converted it to a top level function that would then call lower level functions like so:
>
>
>
>  send_update(Arg1) ->  
>      case ... of
>          {ok, [Val1, Val2]} ->  
>              send_update(check_indices, {Arg1, Val1, Val2});
>          Else ->  
>              lager:error("ERROR: ..")
>      end.
>  send_update(check_indices, {Arg1, Arg2, Arg3}) ->
>      case check_indices(Arg2, Arg3)of
>          true ->
>              send_update(get_values, {Arg1, Arg3});
>          false ->
>              lager:error("EMERGENCY: ….")
>      end;
>  send_update(get_values, {Arg1, Arg2}) ->
>    ...
>      case ... of  
>          {ok, [Val1, Val2, VAl3]} ->
>              send_update(send_value, {Arg1, Val1, Val2, Val3});
>          Error ->  
>              lager:error("ERROR: …")
>      end;
>  send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
>     …
>      Do_something(Args),
>      ok.
>
>
> Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
>
> Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.

This is the universe sending you a message:
1- Some of your messages are supposed indicate dispatch to specific functions.
2- VERY often having a bunch of nested cases means that you have awkward data structures and need to rethink what your program is actually doing.
3- Occasionally you will have a series of checks you need to perform in a specific order -- and there we can use a special form of a fold somtimes called a "pipeline" to normalize this.


Case 1:

Cutting your example down:

send_update(Arg1) ->
  ... .

send_update(check_indices, {Arg1, Arg2, Arg3}) ->
  ... ;
send_update(get_values, {Arg1, Arg2}) ->
  ... ;
send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
  ... .


Why are these all called `send_update/1,2`? They aren't SENDING any UPDATES as far as I can tell. They are DISPATCHING based on the shape of the received input, and that's a totally different thing. In fact, this particular example is recursing over itself in a pretty weird way that confuses the call graph in my mind. Imagine if it had, say, 20 more clauses!

It is quite common to find yourself in need of something analogous to gen_server:handle_cast/2 or handle_call/3.
Quite a lot of the time when you need to reinvent this on your own, however, you are dealing with either unsanitized data, or data with lots of variable arguments. If you have, say, raw messages coming over the wire to a pure Erlang process (a process hand-written using proc_lib instead of being a gen_server or whatever) you'll often have something like:

handle(Incoming, State = #s{buffer = Buffer}) ->
    case interpret(Incoming) of
        {ok, Message, Rest} ->
            dispatch(Message, State#s{buffer = add_to(Buffer, Rest)});
        {error, Error} ->
            ok = log(warning, "Got bad things: ~tp", [Error]),
            terminate()
    end.

dispatch(Message, State = #s{socket = Socket}) ->
    Result =
        case Message of
            {check_indices, {A1, A2, A3}} ->
                check_indices(A1, A2, A3);
            {get_values, {A1, A2}} ->
                get_values(A1, A2);
            {send_value, Values} ->
                send_value(Values, Socket);
            Unknown ->
                ok = log(error, "Probably should avoid this clause because we should die here."),
                terminate()
        end,
    NewState = some_state_update_or_whatever(State),
    {Result, NewState}.


You might need the above procedure to be larger or smaller, have a return send back through the socket inserted somewhere, enter a discreet receive loop as a reaction (which defines a special state of the process as a state machine), have another function do more pre-processing or more post-processing, or whatever. That's not the point. The point is that dispatching and recursively calling different clauses of the same function that have SEMANTICALLY DIFFERENT MEANING is just asking for code that is really hard to read.

Now consider the origin of that convolution -- you were putting way too much in a single clause of a function, and then you resorted to putting way too much into several clauses of the same function, which is a different form of the same confusion.

Break things down, dispatch to specific handlers, pull the result to be returned back to the dispatcher, handle the result in a common way. This isn't always possible, but it nearly always is, which is the reason it is possible to write event loops at all.


Case 2:

When you've never really thought much about what your program is doing it is pretty easy to get rather cavalier with data and wind up with a lot of nested data that is hard to get at, and then you suddenly find that you want to get at the inner data only some of the time and you can shortcut excessive checks with `case` and start turning your nested data problem into a nested `case` problem as well.

The biggest culprit there that I've seen are nested maps and nested records -- and nested maps of nested records and vice-versa.

Whenever you find yourself nesting a lot of data and the data structures themselves start feeling arbitrary and awkward to manipulate -- JUST STOP WHAT YOU'RE DOING. Before you write another universally shared record inside some .hrl file STOP.

Once this point has been reached the universe is telling you that you really need to be writing a module that is an abstract data structure. Write a module that contains an inner state structure of some kind, and (basically) setter/getter/updater functions for it. That's how quite a bit of the stdlib is written, and it is a major headache saver.

Make the exported data type be opaque to external callers so that Dialyzer can catch you when you start getting sneaky and trying to be too intimate with the hidden structure. The reason for this is simple to understand: you didn't understand your data initially so it is likely you still don't quite get it, so you're going to eventually modify the shape of things later. If you are too intimate with the data internals instead of hiding them behind functions then you'll wind up blowing up a lot of code (as you proabably already know). This will leave you with a choice: in the interest of time do you just write another clause next to the existing one, add another `case` layer somewhere, or actually rewrite everything to be "correct"? You'll almost never get the time or motivation to do it write later -- so another level of cases or clause cancer it will usually be.

Ugh. Just evil.


Case 3:

If you have a need to check whether some input is good, the permissions of a user, the context of the request, and the status of some external element before proceeding with an action, how could we do this without 4 levels of `case`? Especially if we want to return a properly detailed error message?

We could trickle-down through a series of functions that essentially curry out the stuff we've dealth with:


handle_request(Request, User, Context, ExternalThingy) ->
    case interpret(Request) of
        {ok, Action} -> handle_request2(Action, User, Context, ExternalThingy);
        Error        -> Error
    end.

handle_request2(Action, User, Context, ExternalThingy) ->
    case check_permission(User, Action) of
        ok    -> handle_request3(Action, User, Context, ExternalThingy);
        Error -> Error
    end.

handle_request3(Action, User, Context, ExternalThingy) ->
    case dispatch_target(Action, Context) of
        {ok, Target} -> handle_request4(Target, User, Context, ExternalThingy);
        Error        -> Error
    end.

handle_request(Target, User, Context, ExternalThingy) ->
    case check_condition(ExternalThingy) of
        ok    -> dispatch(Target, User, Context);
        Error -> Error
    end.

% ...and so on.

Now that's not really so bad, and in some cases it is really the right sort of solution because you might have a lot of variance in what is going on in each step. The case above, however, is so boringly vanilla that WE DON'T EVEN HAVE SPECIAL FUNCTION NAMES. That's a sign of something dumb going on. It is pretty rare that you should ever be writing functions named the same thing plus some incrementing number at the end, or find yourself misunderstanding single assignment to such a degree that you write a long line of updating variables that look like State1, State2, State3, State4. That sort of thing is one more hint to yourself that you're both not understanding the problem and probably trying to write, say, Python in Erlang.

But consider how little variance we had above. Maybe we could do something more like...


pre_check(Action, User, Context, ExternalThingy) ->
    Checks =
        [fun check_request/2,
         fun check_permission/2,
         fun check_dispatch_target/2,
         fun check_condition/2],
    Args = {Action, User, Context, ExternalThingy},
    Harness =
        fun
            (Check, ok)    -> Check(Args);
            (_,     Error) -> Error
        end,
    case lists:foldl(Harness, ok, Checks) of
        ok    -> dispatch(Action, User, Context);
        Error -> Error
    end.


Now the above example is pretty short, only having four checks to perform, but you can probably see quite quickly how we could have eliminated the Args variable entirely there and written a more general form of this function as a list operation (specifically, a non-backtracking, non-transforming pipeline). In this case we have a static thingy (your collection of initial arguments) and a list of thingies we want to run through (your checks). A normal fold would be a list of things to run a single function over, so in this case we have just swapped that around and have a static set of values we want to run a list of operations over. But values are just values, whether they are functions or not, so this is STILL a fold, if you choose to structure it that way.

Not everything lends itself to this kind of structure, of course, but quite often when you see a big chain of `case` statements pushing out past the right side of the screen you've either got a strong indication a pipeline is needed (or that you're looking at machine generated code).

There are several kinds of pipelines you can craft, depending on the situation, and the harness doesn't always have to be a closure (transforming pipelines don't have any need for that, for example).

Anyway....

While I've been typing this out, way past my bedtime and with copious mistakes, bad grammar and typos, I see that some others have already homed in on the first thing I mentioned: those initial atom-based-clause switches should be their own functions. Once you begin to refactor in this direction you will begin to see which sort of situation you are in as far as determining execution flow, and you will also subconsciously begin to EVOLVE A BETTER MODEL of your actual program, the problem it handles, and will probably stumble on a way to make most of the code either move somewhere else or collapse tremendously.

Simplicity tends to follow complexity. Whatever you write, get something working, then refactor it!

-Craig
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

zxq9-2
Dhoh!

On 2017年09月26日 火曜日 00:11:00 you wrote:
> handle_request(Target, User, Context, ExternalThingy) ->

Should be handle_request4/4.

> pre_check(Action, User, Context, ExternalThingy) ->
>     Checks =
>         [fun check_request/2,
>          fun check_permission/2,
>          fun check_dispatch_target/2,
>          fun check_condition/2],

These should all be arity of one, obviously.

I knew something like this was bound to happen at 00:25... ;-)

Anyway, hopefully I explained more than I confused.
The main point remains: you are on the right track and your code will become much nicer to read in short order.

-Craig
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Joe Armstrong-2
In reply to this post by asdf asdf
Good question.

This is example of abnormal termination.

The Erlang 'way' is to write the happy path and, not write twisty little
passages full of error correcting code.

I'll assume send_update has a single argument 'a' or 'b' - and is as follows:

  send_update(Arg1) ->
      {Val1, Val2} = do_this(Arg1),
      {Val3, Val4} = do_that(Arg1),
      Val2+Val3.

   do_this(a) -> {1,2};
   do_this(b) -> {3,4}.

   do_that(a) ->{10,20};
   do_that(b) ->{15,12}.

This is version 1 - no decent errors. Here's what we see in the shell:

   1> 2> demo:send_update(a).
   12
   3> demo:send_update(c).
   ** exception error: no function clause matching demo:do_this(c)
(demo.erl, line 9)
      in function  demo:send_update/1 (demo.erl, line 5)

The error message is not very good - but is enough to get you started in finding
the error. We can get some information like this:

   4> (catch demo:send_update(c)).
    {'EXIT',{function_clause,[{demo,do_this,
                                [c],
                                [{file,"demo.erl"},{line,9}]},
                           {demo,send_update,1,[{file,"demo.erl"},{line,5}]},
  ...

So you can see a stack trace.

But we want better than this. So now I'll make a few small changes.


  send_update_v1(Arg1) ->
      {Val1, Val2} = do_this_v1(Arg1),
      {Val3, Val4} = do_that_v1(Arg1),
      Val2+Val3.

  do_this_v1(a) -> {1,2};
  do_this_v1(b) -> {3,4};
  do_this_v1(X) -> exit({do_this_v1,bad_arg,X}).

  do_that_v1(a) ->{10,20};
  do_that_v1(b) ->{15,12};
  do_that_v1(X) -> exit({do_that_v1,bad_arg,X}).


We can now call this as follows:

   1> demo:send_update_v1(a).
   12

   8> demo:send_update_v1(z).
   ** exception exit: {do_this_v1,bad_arg,z}
      in function  demo:do_this_v1/1 (demo.erl, line 25)
      in call from demo:send_update_v1/1 (demo.erl, line 19)

   9> (catch demo:send_update_v1(z)).
   {'EXIT',{do_this_v1,bad_arg,z}}

Now let's get creative:

 send_update_v2(Arg1) ->
     try send_update_v2_happy(Arg1) of
  X -> X
     catch
  throw:{Type,Val} ->
   lager_error(Type, Val)
     end.

  send_update_v2_happy(Arg1) ->
     {Val1, Val2} = do_this_v2(Arg1),
     {Val3, Val4} = do_that_v2(Arg1),
     Val2+Val3.

  do_this_v2(a) -> {1,2};
  do_this_v2(b) -> {3,4};
  do_this_v2(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).

  do_that_v2(a) ->{10,20};
  do_that_v2(b) ->{15,12};
  do_that_v2(X) -> throw({"ERROR",{v2,bad_arg,X}}).

 lager_error(Type, Val) ->
     io:format("Error:~p ~p~n",[Type,Val]).

Here we retain the happy path code - but add a wrapper that catches
the error and reports it.

  1> demo:send_update_v2(a).
  12
  2> demo:send_update_v2(b).
  19
  3> demo:send_update_v2(c).
  Error:"EMERGENCY" {v1,bad_arg,c}

This code is beginning to look nice - but we're not their yet ...

Lets' stop and think. We've refactored the code into three
parts:

   + the happy path
   + the subroutines that report errors if their arguments are wrong
   + a wrapper that decides what to do with the errors

But there's another problem - in a sense we've provisioned the program
to detect a specific class of errors, and we call lager to report these if
they happen.

What happens if a totally unexpected error creeps in one where we do not
throw an error??

  send_update_v3(Arg1) ->
      try send_update_v3_happy(Arg1) of
    X -> X
      catch
throw:{Type,Val} ->
   lager_error(Type, Val);
error:Why ->
   io:format("Unprovisioned error :: ~p~n",[Why])
      end.

  send_update_v3_happy(Arg1) ->
      {Val1, Val2} = do_this_v3(Arg1),
      {Val3, Val4} = do_that_v3(Arg1),
      Val2+Val3.

  do_this_v3(a) -> {1,2};
  do_this_v3(b) -> {X,Y,Z} = do_that_v3(b),
                   {X+Y,Z};
  do_this_v3(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).

  do_that_v3(a) ->{10,20};
  do_that_v3(b) ->{15,12};
  do_that_v3(X) -> throw({"ERROR",{v3,bad_arg,X}}).

Let's run this code:

    1> demo:send_update_v3(b).
    Unprovisioned error :: {badmatch,{15,12}}
    ok
    2> demo:send_update_v3(a).
    12
    3> demo:send_update_v3(c).
    Error:"EMERGENCY" {v1,bad_arg,c}

Now our program has two type of errors

 - errors we write code to handle (they go the error logger) and
 - totally unexpected errors

BUT in the wrapper that calls the happy case we can distinguish the two.

Again this is good practice.

BUT we can do even better :-)

Errors occur in processes - if we spawn_link a process then the
exception that is caught in the 'try catch end' is propagated to
the link set of the process.

This means we can handle the error in a *remote* process.

As you can see, you can start with a very simple program
(the happy case, no error handling) and refine it by adding
exit(Why) or throw(Why) statements to the code. How
far you go down this path depends upon the program and the
target audience.

You can read more about error handling here:

http://learnyousomeerlang.com/errors-and-exceptions

The complete  program is here:

-module(demo).
-compile(export_all).

send_update(Arg1) ->
    {Val1, Val2} = do_this(Arg1),
    {Val3, Val4} = do_that(Arg1),
    Val2+Val3.

do_this(a) -> {1,2};
do_this(b) -> {3,4}.

do_that(a) ->{10,20};
do_that(b) ->{15,12}.


%% vsn with exit

send_update_v1(Arg1) ->
    {Val1, Val2} = do_this_v1(Arg1),
    {Val3, Val4} = do_that_v1(Arg1),
    Val2+Val3.

do_this_v1(a) -> {1,2};
do_this_v1(b) -> {3,4};
do_this_v1(X) -> exit({do_this_v1,bad_arg,X}).

do_that_v1(a) ->{10,20};
do_that_v1(b) ->{15,12};
do_that_v1(X) -> exit({do_that_v1,bad_arg,X}).

%%  version 2

send_update_v2(Arg1) ->
    try send_update_v2_happy(Arg1) of
X -> X
    catch
throw:{Type,Val} ->
   lager_error(Type, Val)
    end.

send_update_v2_happy(Arg1) ->
    {Val1, Val2} = do_this_v2(Arg1),
    {Val3, Val4} = do_that_v2(Arg1),
    Val2+Val3.

do_this_v2(a) -> {1,2};
do_this_v2(b) -> {3,4};
do_this_v2(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).

do_that_v2(a) ->{10,20};
do_that_v2(b) ->{15,12};
do_that_v2(X) -> throw({"ERROR",{v2,bad_arg,X}}).

lager_error(Type, Val) ->
    io:format("Error:~p ~p~n",[Type,Val]).

%%  version 3

send_update_v3(Arg1) ->
    try send_update_v3_happy(Arg1) of
X -> X
    catch
throw:{Type,Val} ->
   lager_error(Type, Val);
error:Why ->
   io:format("Unprovisioned error :: ~p~n",[Why])
    end.

send_update_v3_happy(Arg1) ->
    {Val1, Val2} = do_this_v3(Arg1),
    {Val3, Val4} = do_that_v3(Arg1),
    Val2+Val3.

do_this_v3(a) -> {1,2};
do_this_v3(b) -> {X,Y,Z} = do_that_v3(b),
                 {X+Y,Z};
do_this_v3(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).

do_that_v3(a) ->{10,20};
do_that_v3(b) ->{15,12};
do_that_v3(X) -> throw({"ERROR",{v3,bad_arg,X}}).

Cheers

/Joe

On Mon, Sep 25, 2017 at 3:25 PM, code wiget <[hidden email]> wrote:

> Hello everyone,
>
> As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
>
> Send_update(Arg1) ->
>         case do this(Arg1) of
>                 {ok, [Val1, Val2]} ->
>                         case do_that(Val1, Val2) of
>                                 {ok, [Val3, Val4]} ->
>                                         case do_this2(…) of
> ….
>
> It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
>
> So I went and I converted it to a top level function that would then call lower level functions like so:
>
>
>
>  send_update(Arg1) ->
>      case ... of
>          {ok, [Val1, Val2]} ->
>              send_update(check_indices, {Arg1, Val1, Val2});
>          Else ->
>              lager:error("ERROR: ..")
>      end.
>  send_update(check_indices, {Arg1, Arg2, Arg3}) ->
>      case check_indices(Arg2, Arg3)of
>          true ->
>              send_update(get_values, {Arg1, Arg3});
>          false ->
>              lager:error("EMERGENCY: ….")
>      end;
>  send_update(get_values, {Arg1, Arg2}) ->
>    ...
>      case ... of
>          {ok, [Val1, Val2, VAl3]} ->
>              send_update(send_value, {Arg1, Val1, Val2, Val3});
>          Error ->
>              lager:error("ERROR: …")
>      end;
>  send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
>     …
>      Do_something(Args),
>      ok.
>
>
> Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
>
> Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
>
> Thank you for your advice!
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Joe Armstrong-2
This thread has had some pretty long replies.

To summarize:

   - code the happy path (ie no nested cases, just what you expect to happen)
   - add exit(Why) code at points in your code where you realise
     something has gone wrong

  This is what I call "let it crash"

   Then - if you need it - processes the error *somewhere else* ie NOT
at the place where it occurred.

   Erlang is a concurrent language - we have many processes running it's not
a biggie if a few of them crash. In a sequential language with one thread
it's a big deal if your one process crashes - so sequential programs spend
all their effort checking things and trying not to crash. We do the opposite :-)

/Joe






On Mon, Sep 25, 2017 at 5:57 PM, Joe Armstrong <[hidden email]> wrote:

> Good question.
>
> This is example of abnormal termination.
>
> The Erlang 'way' is to write the happy path and, not write twisty little
> passages full of error correcting code.
>
> I'll assume send_update has a single argument 'a' or 'b' - and is as follows:
>
>   send_update(Arg1) ->
>       {Val1, Val2} = do_this(Arg1),
>       {Val3, Val4} = do_that(Arg1),
>       Val2+Val3.
>
>    do_this(a) -> {1,2};
>    do_this(b) -> {3,4}.
>
>    do_that(a) ->{10,20};
>    do_that(b) ->{15,12}.
>
> This is version 1 - no decent errors. Here's what we see in the shell:
>
>    1> 2> demo:send_update(a).
>    12
>    3> demo:send_update(c).
>    ** exception error: no function clause matching demo:do_this(c)
> (demo.erl, line 9)
>       in function  demo:send_update/1 (demo.erl, line 5)
>
> The error message is not very good - but is enough to get you started in finding
> the error. We can get some information like this:
>
>    4> (catch demo:send_update(c)).
>     {'EXIT',{function_clause,[{demo,do_this,
>                                 [c],
>                                 [{file,"demo.erl"},{line,9}]},
>                            {demo,send_update,1,[{file,"demo.erl"},{line,5}]},
>   ...
>
> So you can see a stack trace.
>
> But we want better than this. So now I'll make a few small changes.
>
>
>   send_update_v1(Arg1) ->
>       {Val1, Val2} = do_this_v1(Arg1),
>       {Val3, Val4} = do_that_v1(Arg1),
>       Val2+Val3.
>
>   do_this_v1(a) -> {1,2};
>   do_this_v1(b) -> {3,4};
>   do_this_v1(X) -> exit({do_this_v1,bad_arg,X}).
>
>   do_that_v1(a) ->{10,20};
>   do_that_v1(b) ->{15,12};
>   do_that_v1(X) -> exit({do_that_v1,bad_arg,X}).
>
>
> We can now call this as follows:
>
>    1> demo:send_update_v1(a).
>    12
>
>    8> demo:send_update_v1(z).
>    ** exception exit: {do_this_v1,bad_arg,z}
>       in function  demo:do_this_v1/1 (demo.erl, line 25)
>       in call from demo:send_update_v1/1 (demo.erl, line 19)
>
>    9> (catch demo:send_update_v1(z)).
>    {'EXIT',{do_this_v1,bad_arg,z}}
>
> Now let's get creative:
>
>  send_update_v2(Arg1) ->
>      try send_update_v2_happy(Arg1) of
>   X -> X
>      catch
>   throw:{Type,Val} ->
>    lager_error(Type, Val)
>      end.
>
>   send_update_v2_happy(Arg1) ->
>      {Val1, Val2} = do_this_v2(Arg1),
>      {Val3, Val4} = do_that_v2(Arg1),
>      Val2+Val3.
>
>   do_this_v2(a) -> {1,2};
>   do_this_v2(b) -> {3,4};
>   do_this_v2(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).
>
>   do_that_v2(a) ->{10,20};
>   do_that_v2(b) ->{15,12};
>   do_that_v2(X) -> throw({"ERROR",{v2,bad_arg,X}}).
>
>  lager_error(Type, Val) ->
>      io:format("Error:~p ~p~n",[Type,Val]).
>
> Here we retain the happy path code - but add a wrapper that catches
> the error and reports it.
>
>   1> demo:send_update_v2(a).
>   12
>   2> demo:send_update_v2(b).
>   19
>   3> demo:send_update_v2(c).
>   Error:"EMERGENCY" {v1,bad_arg,c}
>
> This code is beginning to look nice - but we're not their yet ...
>
> Lets' stop and think. We've refactored the code into three
> parts:
>
>    + the happy path
>    + the subroutines that report errors if their arguments are wrong
>    + a wrapper that decides what to do with the errors
>
> But there's another problem - in a sense we've provisioned the program
> to detect a specific class of errors, and we call lager to report these if
> they happen.
>
> What happens if a totally unexpected error creeps in one where we do not
> throw an error??
>
>   send_update_v3(Arg1) ->
>       try send_update_v3_happy(Arg1) of
>     X -> X
>       catch
> throw:{Type,Val} ->
>    lager_error(Type, Val);
> error:Why ->
>    io:format("Unprovisioned error :: ~p~n",[Why])
>       end.
>
>   send_update_v3_happy(Arg1) ->
>       {Val1, Val2} = do_this_v3(Arg1),
>       {Val3, Val4} = do_that_v3(Arg1),
>       Val2+Val3.
>
>   do_this_v3(a) -> {1,2};
>   do_this_v3(b) -> {X,Y,Z} = do_that_v3(b),
>                    {X+Y,Z};
>   do_this_v3(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).
>
>   do_that_v3(a) ->{10,20};
>   do_that_v3(b) ->{15,12};
>   do_that_v3(X) -> throw({"ERROR",{v3,bad_arg,X}}).
>
> Let's run this code:
>
>     1> demo:send_update_v3(b).
>     Unprovisioned error :: {badmatch,{15,12}}
>     ok
>     2> demo:send_update_v3(a).
>     12
>     3> demo:send_update_v3(c).
>     Error:"EMERGENCY" {v1,bad_arg,c}
>
> Now our program has two type of errors
>
>  - errors we write code to handle (they go the error logger) and
>  - totally unexpected errors
>
> BUT in the wrapper that calls the happy case we can distinguish the two.
>
> Again this is good practice.
>
> BUT we can do even better :-)
>
> Errors occur in processes - if we spawn_link a process then the
> exception that is caught in the 'try catch end' is propagated to
> the link set of the process.
>
> This means we can handle the error in a *remote* process.
>
> As you can see, you can start with a very simple program
> (the happy case, no error handling) and refine it by adding
> exit(Why) or throw(Why) statements to the code. How
> far you go down this path depends upon the program and the
> target audience.
>
> You can read more about error handling here:
>
> http://learnyousomeerlang.com/errors-and-exceptions
>
> The complete  program is here:
>
> -module(demo).
> -compile(export_all).
>
> send_update(Arg1) ->
>     {Val1, Val2} = do_this(Arg1),
>     {Val3, Val4} = do_that(Arg1),
>     Val2+Val3.
>
> do_this(a) -> {1,2};
> do_this(b) -> {3,4}.
>
> do_that(a) ->{10,20};
> do_that(b) ->{15,12}.
>
>
> %% vsn with exit
>
> send_update_v1(Arg1) ->
>     {Val1, Val2} = do_this_v1(Arg1),
>     {Val3, Val4} = do_that_v1(Arg1),
>     Val2+Val3.
>
> do_this_v1(a) -> {1,2};
> do_this_v1(b) -> {3,4};
> do_this_v1(X) -> exit({do_this_v1,bad_arg,X}).
>
> do_that_v1(a) ->{10,20};
> do_that_v1(b) ->{15,12};
> do_that_v1(X) -> exit({do_that_v1,bad_arg,X}).
>
> %%  version 2
>
> send_update_v2(Arg1) ->
>     try send_update_v2_happy(Arg1) of
> X -> X
>     catch
> throw:{Type,Val} ->
>    lager_error(Type, Val)
>     end.
>
> send_update_v2_happy(Arg1) ->
>     {Val1, Val2} = do_this_v2(Arg1),
>     {Val3, Val4} = do_that_v2(Arg1),
>     Val2+Val3.
>
> do_this_v2(a) -> {1,2};
> do_this_v2(b) -> {3,4};
> do_this_v2(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).
>
> do_that_v2(a) ->{10,20};
> do_that_v2(b) ->{15,12};
> do_that_v2(X) -> throw({"ERROR",{v2,bad_arg,X}}).
>
> lager_error(Type, Val) ->
>     io:format("Error:~p ~p~n",[Type,Val]).
>
> %%  version 3
>
> send_update_v3(Arg1) ->
>     try send_update_v3_happy(Arg1) of
> X -> X
>     catch
> throw:{Type,Val} ->
>    lager_error(Type, Val);
> error:Why ->
>    io:format("Unprovisioned error :: ~p~n",[Why])
>     end.
>
> send_update_v3_happy(Arg1) ->
>     {Val1, Val2} = do_this_v3(Arg1),
>     {Val3, Val4} = do_that_v3(Arg1),
>     Val2+Val3.
>
> do_this_v3(a) -> {1,2};
> do_this_v3(b) -> {X,Y,Z} = do_that_v3(b),
>                  {X+Y,Z};
> do_this_v3(X) -> throw({"EMERGENCY",{v1,bad_arg,X}}).
>
> do_that_v3(a) ->{10,20};
> do_that_v3(b) ->{15,12};
> do_that_v3(X) -> throw({"ERROR",{v3,bad_arg,X}}).
>
> Cheers
>
> /Joe
>
> On Mon, Sep 25, 2017 at 3:25 PM, code wiget <[hidden email]> wrote:
>> Hello everyone,
>>
>> As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
>>
>> Send_update(Arg1) ->
>>         case do this(Arg1) of
>>                 {ok, [Val1, Val2]} ->
>>                         case do_that(Val1, Val2) of
>>                                 {ok, [Val3, Val4]} ->
>>                                         case do_this2(…) of
>> ….
>>
>> It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
>>
>> So I went and I converted it to a top level function that would then call lower level functions like so:
>>
>>
>>
>>  send_update(Arg1) ->
>>      case ... of
>>          {ok, [Val1, Val2]} ->
>>              send_update(check_indices, {Arg1, Val1, Val2});
>>          Else ->
>>              lager:error("ERROR: ..")
>>      end.
>>  send_update(check_indices, {Arg1, Arg2, Arg3}) ->
>>      case check_indices(Arg2, Arg3)of
>>          true ->
>>              send_update(get_values, {Arg1, Arg3});
>>          false ->
>>              lager:error("EMERGENCY: ….")
>>      end;
>>  send_update(get_values, {Arg1, Arg2}) ->
>>    ...
>>      case ... of
>>          {ok, [Val1, Val2, VAl3]} ->
>>              send_update(send_value, {Arg1, Val1, Val2, Val3});
>>          Error ->
>>              lager:error("ERROR: …")
>>      end;
>>  send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
>>     …
>>      Do_something(Args),
>>      ok.
>>
>>
>> Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
>>
>> Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
>>
>> Thank you for your advice!
>> _______________________________________________
>> erlang-questions mailing list
>> [hidden email]
>> http://erlang.org/mailman/listinfo/erlang-questions
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Richard A. O'Keefe-2
In reply to this post by asdf asdf



>  send_update(Arg1) ->
>      case ... of
>          {ok, [Val1, Val2]} ->
>              send_update(check_indices, {Arg1, Val1, Val2});
>          Else ->
>              lager:error("ERROR: ..")
>      end.
>  send_update(check_indices, {Arg1, Arg2, Arg3}) ->
>      case check_indices(Arg2, Arg3)of
>          true ->
>              send_update(get_values, {Arg1, Arg3});
>          false ->
>              lager:error("EMERGENCY: ….")
>      end;

Why is this not

     send_update(Arg1) ->
         case ...
           of {ok, [Val1, Val2]} ->
                  check_indices(Val1, Val2, Arg1)
            ; _ ->
                  lager:error("ERROR: ...")
         end.

     check_indices(Val1, Val2, Arg1) ->
         case check_indices(Val1, Val2)
           of true  -> get_values(Val2, Arg1)
            ; false -> lager:error("EMERGENCY: ...")
         end.

When you break a large function into lots of little
functions, the little functions should have their
own names and their own comments.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Richard A. O'Keefe-2


> Why is this not
>
>     send_update(Arg1) ->
>         case ...
>           of {ok, [Val1, Val2]} ->
>                  check_indices(Val1, Val2, Arg1)
>            ; _ ->
>                  lager:error("ERROR: ...")
>         end.
>
>     check_indices(Val1, Val2, Arg1) ->
>         case check_indices(Val1, Val2)
>           of true  -> get_values(Val2, Arg1)
>            ; false -> lager:error("EMERGENCY: ...")
>         end.
>
> When you break a large function into lots of little
> functions, the little functions should have their
> own names and their own comments.

What others have written about this is excellent.
When the original poster talked about 'case', I
presumed that the original code had *multiple*
success clauses in the 'case' forms, and that
single success clauses were being shown simply to
keep the example short.

If that's not so, then indeed the "keep the happy
path clean" approach is definitely the way to go.

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

dploop@163.com
In reply to this post by asdf asdf
This problem also bothered me for a long time, so I write a parse transform to deal with it. To be specific, say we want to write a function which takes two integers and returns  the product and quotient of them.

muldiv(First, Second) ->
    case is_integer(First) of
        true ->
            case is_integer(Second) of
                true ->
                    Product = First * Second,
                    case Second =/= 0 of
                        true ->
                            Quotient = First div Second,
                            {ok, {Product, Quotient}};
                        false ->
                            {error, "Second must not be zero!"}
                    end;
                false ->
                    {error, "Second must be an integer!"}
            end;
        false ->
            {error, "First must be an integer!"}
    end.

Ugly, at least I think this "rocket" is not beautiful. Even worse, if requirement changed, you should modify this code on a large scale. To avoid this problem, you can just rewrite your code like this
muldiv(First, Second) ->
    do@([esugar_do_transform_error ||
        case is_integer(First) of
        true -> return(next);
        false -> fail("First must be an integer!")
        end,
        case is_integer(Second) of
        true -> return(next);
        false -> fail("Second must be an integer!")
        end,
        Product = First * Second,
        case Second =/= 0 of
        true -> return(next);
        false -> fail("Second must not be zero!")
        end,
        Quotient = First div Second,
        return({Product, Quotient})
    ]).

This idea came from the Haskell's do grammer, and to use this grammer, you just need to write a error monad.

This parse transform code is one module of my esugar(An Erlang Syntactic Sugar Library) library, and the address is (https://github.com/dploop/esugar) 

I'm glad to discuss with you about how to use it or improve it(because I just write this toy for fun at the begining).



 
Date: 2017-09-25 21:25
Subject: [erlang-questions] Nested Case Statements v.s. multiple functions
Hello everyone,
 
As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
 
Send_update(Arg1) ->
case do this(Arg1) of
{ok, [Val1, Val2]} -> 
case do_that(Val1, Val2) of
{ok, [Val3, Val4]} ->
case do_this2(…) of
….
 
It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
 
So I went and I converted it to a top level function that would then call lower level functions like so:
 
 
send_update(Arg1) -> 
     case ... of
         {ok, [Val1, Val2]} -> 
             send_update(check_indices, {Arg1, Val1, Val2});
         Else -> 
             lager:error("ERROR: ..")
     end.
send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of 
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error -> 
             lager:error("ERROR: …")
     end;
send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.
 
 
Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
 
Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
 
Thank you for your advice!
_______________________________________________
erlang-questions mailing list
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Michael Truog
On 09/25/2017 07:40 PM, [hidden email] wrote:
This problem also bothered me for a long time, so I write a parse transform to deal with it. To be specific, say we want to write a function which takes two integers and returns  the product and quotient of them.

muldiv(First, Second) ->
    case is_integer(First) of
        true ->
            case is_integer(Second) of
                true ->
                    Product = First * Second,
                    case Second =/= 0 of
                        true ->
                            Quotient = First div Second,
                            {ok, {Product, Quotient}};
                        false ->
                            {error, "Second must not be zero!"}
                    end;
                false ->
                    {error, "Second must be an integer!"}
            end;
        false ->
            {error, "First must be an integer!"}
    end.

Obsessing over a rocket shape doesn't seem as important as the absence of any guard expression usage here.  Guards are unfortunately often ignored by Erlang programmers, though they are the one thing in Erlang without side-effects.  Ideally, the source code is written to break when bad input is provided, and that doesn't require that you define the exception thrown for bad input.  A more minimal example without the added complexity here is below:

muldiv(First, 0)
    when is_integer(First) ->
    0;
muldiv(First, Second)
    when is_integer(First), is_integer(Second) ->
    {ok, {First * Second, First div Second}}.

That gets rid of all the noise and useless error returns, and instead lets the process die when there is a problem, unless the exception is caught and handled.  You could add another function clause to do an error/exit/throw on bad input, so it is a clear misuse of a function which would be checked with dialyzer, and dialyzer should be capable of inferring the types from the guards (the current code above should be known to only accept integers, and other usage would cause a dialyzer error... adding a bad input function clause would break that, but then that could be fixed by the Erlang type specification), even if the Erlang type specification isn't added.

Best Regards,
Michael

Ugly, at least I think this "rocket" is not beautiful. Even worse, if requirement changed, you should modify this code on a large scale. To avoid this problem, you can just rewrite your code like this
muldiv(First, Second) ->
    do@([esugar_do_transform_error ||
        case is_integer(First) of
        true -> return(next);
        false -> fail("First must be an integer!")
        end,
        case is_integer(Second) of
        true -> return(next);
        false -> fail("Second must be an integer!")
        end,
        Product = First * Second,
        case Second =/= 0 of
        true -> return(next);
        false -> fail("Second must not be zero!")
        end,
        Quotient = First div Second,
        return({Product, Quotient})
    ]).

This idea came from the Haskell's do grammer, and to use this grammer, you just need to write a error monad.

This parse transform code is one module of my esugar(An Erlang Syntactic Sugar Library) library, and the address is (https://github.com/dploop/esugar) 

I'm glad to discuss with you about how to use it or improve it(because I just write this toy for fun at the begining).



 
Date: 2017-09-25 21:25
Subject: [erlang-questions] Nested Case Statements v.s. multiple functions
Hello everyone,
 
As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
 
Send_update(Arg1) ->
case do this(Arg1) of
{ok, [Val1, Val2]} -> 
case do_that(Val1, Val2) of
{ok, [Val3, Val4]} ->
case do_this2(…) of
….
 
It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
 
So I went and I converted it to a top level function that would then call lower level functions like so:
 
 
send_update(Arg1) -> 
     case ... of
         {ok, [Val1, Val2]} -> 
             send_update(check_indices, {Arg1, Val1, Val2});
         Else -> 
             lager:error("ERROR: ..")
     end.
send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of 
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error -> 
             lager:error("ERROR: …")
     end;
send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.
 
 
Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
 
Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
 
Thank you for your advice!
_______________________________________________
erlang-questions mailing list


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

dploop@163.com
> "Obsessing over a rocket shape doesn't seem as important as the absence of any guard expression usage here.  Guards are unfortunately often ignored by Erlang programmers, though they are the one thing in Erlang without side-effects."

is_integer, is_zero is just a simplified example, and by coincidence, they can use in the guard. But in most situations you can't use guard, for example in game industry when player want to buy equipment, you should check if his coin is sufficient, and then you should check the number of equipment is right and then you  should check if the equipment is valid to exchange and so on. The complex business logic can't be easily solved by guard, and Nested Cases is inevitable.



 
Date: 2017-09-26 12:48
Subject: Re: [erlang-questions] Nested Case Statements v.s. multiple functions
On 09/25/2017 07:40 PM, [hidden email] wrote:
This problem also bothered me for a long time, so I write a parse transform to deal with it. To be specific, say we want to write a function which takes two integers and returns  the product and quotient of them.

muldiv(First, Second) ->
    case is_integer(First) of
        true ->
            case is_integer(Second) of
                true ->
                    Product = First * Second,
                    case Second =/= 0 of
                        true ->
                            Quotient = First div Second,
                            {ok, {Product, Quotient}};
                        false ->
                            {error, "Second must not be zero!"}
                    end;
                false ->
                    {error, "Second must be an integer!"}
            end;
        false ->
            {error, "First must be an integer!"}
    end.

Obsessing over a rocket shape doesn't seem as important as the absence of any guard expression usage here.  Guards are unfortunately often ignored by Erlang programmers, though they are the one thing in Erlang without side-effects.  Ideally, the source code is written to break when bad input is provided, and that doesn't require that you define the exception thrown for bad input.  A more minimal example without the added complexity here is below:

muldiv(First, 0)
    when is_integer(First) ->
    0;
muldiv(First, Second)
    when is_integer(First), is_integer(Second) ->
    {ok, {First * Second, First div Second}}.

That gets rid of all the noise and useless error returns, and instead lets the process die when there is a problem, unless the exception is caught and handled.  You could add another function clause to do an error/exit/throw on bad input, so it is a clear misuse of a function which would be checked with dialyzer, and dialyzer should be capable of inferring the types from the guards (the current code above should be known to only accept integers, and other usage would cause a dialyzer error... adding a bad input function clause would break that, but then that could be fixed by the Erlang type specification), even if the Erlang type specification isn't added.

Best Regards,
Michael

Ugly, at least I think this "rocket" is not beautiful. Even worse, if requirement changed, you should modify this code on a large scale. To avoid this problem, you can just rewrite your code like this
muldiv(First, Second) ->
    do@([esugar_do_transform_error ||
        case is_integer(First) of
        true -> return(next);
        false -> fail("First must be an integer!")
        end,
        case is_integer(Second) of
        true -> return(next);
        false -> fail("Second must be an integer!")
        end,
        Product = First * Second,
        case Second =/= 0 of
        true -> return(next);
        false -> fail("Second must not be zero!")
        end,
        Quotient = First div Second,
        return({Product, Quotient})
    ]).

This idea came from the Haskell's do grammer, and to use this grammer, you just need to write a error monad.

This parse transform code is one module of my esugar(An Erlang Syntactic Sugar Library) library, and the address is (https://github.com/dploop/esugar) 

I'm glad to discuss with you about how to use it or improve it(because I just write this toy for fun at the begining).



 
Date: 2017-09-25 21:25
Subject: [erlang-questions] Nested Case Statements v.s. multiple functions
Hello everyone,
 
As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
 
Send_update(Arg1) ->
case do this(Arg1) of
{ok, [Val1, Val2]} -> 
case do_that(Val1, Val2) of
{ok, [Val3, Val4]} ->
case do_this2(…) of
….
 
It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
 
So I went and I converted it to a top level function that would then call lower level functions like so:
 
 
send_update(Arg1) -> 
     case ... of
         {ok, [Val1, Val2]} -> 
             send_update(check_indices, {Arg1, Val1, Val2});
         Else -> 
             lager:error("ERROR: ..")
     end.
send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of 
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error -> 
             lager:error("ERROR: …")
     end;
send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.
 
 
Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
 
Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
 
Thank you for your advice!
_______________________________________________
erlang-questions mailing list


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Gleb Vinogradov
In reply to this post by dploop@163.com
Well, this example could be easily written with Erlang _if_ operator:

muldiv(First, Second) ->
    if
        not is_integer(First) -> {error, "First must be an integer"};
        not is_integer(Second) -> {error, "Second must be an integer"};
        Second =:= 0 ->  {error, "Second must not be zero!"};
        true ->
            Quotient = First div Second,
            Product = First * Second,
            {ok, {Product, Quotient}}
    end.



2017-09-26 9:40 GMT+07:00 [hidden email] <[hidden email]>:
This problem also bothered me for a long time, so I write a parse transform to deal with it. To be specific, say we want to write a function which takes two integers and returns  the product and quotient of them.

muldiv(First, Second) ->
    case is_integer(First) of
        true ->
            case is_integer(Second) of
                true ->
                    Product = First * Second,
                    case Second =/= 0 of
                        true ->
                            Quotient = First div Second,
                            {ok, {Product, Quotient}};
                        false ->
                            {error, "Second must not be zero!"}
                    end;
                false ->
                    {error, "Second must be an integer!"}
            end;
        false ->
            {error, "First must be an integer!"}
    end.

Ugly, at least I think this "rocket" is not beautiful. Even worse, if requirement changed, you should modify this code on a large scale. To avoid this problem, you can just rewrite your code like this
muldiv(First, Second) ->
    do@([esugar_do_transform_error ||
        case is_integer(First) of
        true -> return(next);
        false -> fail("First must be an integer!")
        end,
        case is_integer(Second) of
        true -> return(next);
        false -> fail("Second must be an integer!")
        end,
        Product = First * Second,
        case Second =/= 0 of
        true -> return(next);
        false -> fail("Second must not be zero!")
        end,
        Quotient = First div Second,
        return({Product, Quotient})
    ]).

This idea came from the Haskell's do grammer, and to use this grammer, you just need to write a error monad.

This parse transform code is one module of my esugar(An Erlang Syntactic Sugar Library) library, and the address is (https://github.com/dploop/esugar) 

I'm glad to discuss with you about how to use it or improve it(because I just write this toy for fun at the begining).



 
Date: 2017-09-25 21:25
Subject: [erlang-questions] Nested Case Statements v.s. multiple functions
Hello everyone,
 
As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
 
Send_update(Arg1) ->
case do this(Arg1) of
{ok, [Val1, Val2]} -> 
case do_that(Val1, Val2) of
{ok, [Val3, Val4]} ->
case do_this2(…) of
….
 
It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
 
So I went and I converted it to a top level function that would then call lower level functions like so:
 
 
send_update(Arg1) -> 
     case ... of
         {ok, [Val1, Val2]} -> 
             send_update(check_indices, {Arg1, Val1, Val2});
         Else -> 
             lager:error("ERROR: ..")
     end.
send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of 
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error -> 
             lager:error("ERROR: …")
     end;
send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.
 
 
Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
 
Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
 
Thank you for your advice!
_______________________________________________
erlang-questions mailing list

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions



_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Michael Truog
In reply to this post by dploop@163.com
On 09/25/2017 10:16 PM, dploop wrote:
> "Obsessing over a rocket shape doesn't seem as important as the absence of any guard expression usage here.  Guards are unfortunately often ignored by Erlang programmers, though they are the one thing in Erlang without side-effects."

is_integer, is_zero is just a simplified example, and by coincidence, they can use in the guard. But in most situations you can't use guard, for example in game industry when player want to buy equipment, you should check if his coin is sufficient, and then you should check the number of equipment is right and then you  should check if the equipment is valid to exchange and so on. The complex business logic can't be easily solved by guard, and Nested Cases is inevitable.

Complex business logic can be solved by guard expressions and the only potential difference is declaring more variables or more function clauses, depending on how the problem is abstracted.  Guards are specifically for logic, so why not use them to express that logic?
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

zxq9-2
In reply to this post by Michael Truog
On 2017年09月25日 月曜日 21:48:19 Michael Truog wrote:
> Guards are unfortunately often ignored by Erlang programmers

Indeed!
Match! Guard! Crash otherwise!

WHY DO YOU NOT KNOW WHAT DATA YOU ARE RECEIVING?!?!?!?!?
AAAAAHHHHHHH!!!

Sorry for the outburst, I just wanted to put this into emotional context
for newcomers. This is a really, really big deal.

-Craig

Also, macros and parse transforms are the work of the devil... in most cases.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Brady Powers
In reply to this post by dploop@163.com
While I agree guards are often ignored, they're often ignored because they're limited. I'd rather be able to write my own guard functions, with the warning that they need to be side effect free and performant, than be able to use them only 10% of the time.

However that doesn't excuse the multiple cases here. I do often use cases to validate input, because guards often won't do, however you only need one!

muldiv(First, Second) ->
    case {is_integer(First), is_integer(Second)} of
        {true, true} -> 
            Quotient = First div Second,
            Product = First * Second,
            {ok, {Product, Quotient}}
    end.

But in an ideal world, I could write my own guard functions so that, when its not a trivial is_integer/1, I can still use guards.

On Tuesday, September 26, 2017, 1:23:28 AM EDT, dploop <[hidden email]> wrote:


> "Obsessing over a rocket shape doesn't seem as important as the absence of any guard expression usage here.  Guards are unfortunately often ignored by Erlang programmers, though they are the one thing in Erlang without side-effects."

is_integer, is_zero is just a simplified example, and by coincidence, they can use in the guard. But in most situations you can't use guard, for example in game industry when player want to buy equipment, you should check if his coin is sufficient, and then you should check the number of equipment is right and then you  should check if the equipment is valid to exchange and so on. The complex business logic can't be easily solved by guard, and Nested Cases is inevitable.



 
Date: 2017-09-26 12:48
Subject: Re: [erlang-questions] Nested Case Statements v.s. multiple functions
On 09/25/2017 07:40 PM, [hidden email] wrote:
This problem also bothered me for a long time, so I write a parse transform to deal with it. To be specific, say we want to write a function which takes two integers and returns  the product and quotient of them.

muldiv(First, Second) ->
    case is_integer(First) of
        true ->
            case is_integer(Second) of
                true ->
                    Product = First * Second,
                    case Second =/= 0 of
                        true ->
                            Quotient = First div Second,
                            {ok, {Product, Quotient}};
                        false ->
                            {error, "Second must not be zero!"}
                    end;
                false ->
                    {error, "Second must be an integer!"}
            end;
        false ->
            {error, "First must be an integer!"}
    end.

Obsessing over a rocket shape doesn't seem as important as the absence of any guard expression usage here.  Guards are unfortunately often ignored by Erlang programmers, though they are the one thing in Erlang without side-effects.  Ideally, the source code is written to break when bad input is provided, and that doesn't require that you define the exception thrown for bad input.  A more minimal example without the added complexity here is below:

muldiv(First, 0)
    when is_integer(First) ->
    0;
muldiv(First, Second)
    when is_integer(First), is_integer(Second) ->
    {ok, {First * Second, First div Second}}.

That gets rid of all the noise and useless error returns, and instead lets the process die when there is a problem, unless the exception is caught and handled.  You could add another function clause to do an error/exit/throw on bad input, so it is a clear misuse of a function which would be checked with dialyzer, and dialyzer should be capable of inferring the types from the guards (the current code above should be known to only accept integers, and other usage would cause a dialyzer error... adding a bad input function clause would break that, but then that could be fixed by the Erlang type specification), even if the Erlang type specification isn't added.

Best Regards,
Michael

Ugly, at least I think this "rocket" is not beautiful. Even worse, if requirement changed, you should modify this code on a large scale. To avoid this problem, you can just rewrite your code like this
muldiv(First, Second) ->
    do@([esugar_do_transform_error ||
        case is_integer(First) of
        true -> return(next);
        false -> fail("First must be an integer!")
        end,
        case is_integer(Second) of
        true -> return(next);
        false -> fail("Second must be an integer!")
        end,
        Product = First * Second,
        case Second =/= 0 of
        true -> return(next);
        false -> fail("Second must not be zero!")
        end,
        Quotient = First div Second,
        return({Product, Quotient})
    ]).

This idea came from the Haskell's do grammer, and to use this grammer, you just need to write a error monad.

This parse transform code is one module of my esugar(An Erlang Syntactic Sugar Library) library, and the address is (https://github.com/dploop/esugar) 

I'm glad to discuss with you about how to use it or improve it(because I just write this toy for fun at the begining).



 
Date: 2017-09-25 21:25
Subject: [erlang-questions] Nested Case Statements v.s. multiple functions
Hello everyone,
 
As I get further into Erlang, I am starting to realize that some of my functions have been getting pretty ugly with nested case statements. For example, I had a nested case statement that looked something like this:
 
Send_update(Arg1) ->
case do this(Arg1) of
{ok, [Val1, Val2]} -> 
case do_that(Val1, Val2) of
{ok, [Val3, Val4]} ->
case do_this2(…) of
….
 
It continued into this for another few functions, you get the picture - its ugly, and it is hard to read.
 
So I went and I converted it to a top level function that would then call lower level functions like so:
 
 
send_update(Arg1) -> 
     case ... of
         {ok, [Val1, Val2]} -> 
             send_update(check_indices, {Arg1, Val1, Val2});
         Else -> 
             lager:error("ERROR: ..")
     end.
send_update(check_indices, {Arg1, Arg2, Arg3}) ->
     case check_indices(Arg2, Arg3)of
         true ->
             send_update(get_values, {Arg1, Arg3});
         false ->
             lager:error("EMERGENCY: ….")
     end;
send_update(get_values, {Arg1, Arg2}) ->
   ...
     case ... of 
         {ok, [Val1, Val2, VAl3]} ->
             send_update(send_value, {Arg1, Val1, Val2, Val3});
         Error -> 
             lager:error("ERROR: …")
     end;
send_update(send_value, {Arg1, Arg2, Arg3, Arg4}) ->
    …
     Do_something(Args),
     ok.
 
 
Now that I look at it though, both don’t look right. They don’t look like something I would write in any other language where I would just have if’s and else’s.
 
Is this the proper way to write Erlang? I know everyone has their own style, but I assume there is some accepted form of writing functional programs with deep nests.
 
Thank you for your advice!
_______________________________________________
erlang-questions mailing list


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

asdf asdf
In reply to this post by zxq9-2
Thank you all for your replies, I went ahead and changed my function names and added guards, and it now looks far cleaner.

One thing that I keep seeing in this thread though is some variant of “crash early, crash often,” and this is a little troubling. What if you are writing a program that is receiving a messages and has a queue awaiting action? If the program dies, those messages will be lost, and if the calling processes made ‘casts’ then those messages won’t ever be delivered or processed. Also, it takes a bit of time for the supervisor to re-initialize the process, and this could be bad. 

Is it always a good idea to “crash often” when bad input is received?

Thanks for all of your help!

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Joe Armstrong-2
On Tue, Sep 26, 2017 at 5:43 PM, code wiget <[hidden email]> wrote:

> Thank you all for your replies, I went ahead and changed my function names
> and added guards, and it now looks far cleaner.
>
> One thing that I keep seeing in this thread though is some variant of “crash
> early, crash often,” and this is a little troubling. What if you are writing
> a program that is receiving a messages and has a queue awaiting action? If
> the program dies, those messages will be lost, and if the calling processes
> made ‘casts’ then those messages won’t ever be delivered or processed. Also,
> it takes a bit of time for the supervisor to re-initialize the process, and
> this could be bad.
>
> Is it always a good idea to “crash often” when bad input is received?

You have to make your mind up. Choose between:

   + terminate the entire erlang system
   + crash the entire computer
   + terminate the process receiving the bad data
   + throw away the bad input and continue
   + crash the program that send you the bad data
   + many more things ...

This is part of the design of your application and has nothing to do
with Erlang.

Make your mind up about what you want to do if things go wrong
then write the code to do it.

Hint: write something to a log on stable storage *before* crashing
you should be able to do a post-mortem analysis of the crash.

/Joe


>
> Thanks for all of your help!
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

zxq9-2
In reply to this post by asdf asdf
On 2017年09月26日 火曜日 11:43:54 you wrote:
> Thank you all for your replies, I went ahead and changed my function names and added guards, and it now looks far cleaner.
>
> One thing that I keep seeing in this thread though is some variant of “crash early, crash often,” and this is a little troubling. What if you are writing a program that is receiving a messages and has a queue awaiting action? If the program dies, those messages will be lost, and if the calling processes made ‘casts’ then those messages won’t ever be delivered or processed. Also, it takes a bit of time for the supervisor to re-initialize the process, and this could be bad.
>
> Is it always a good idea to “crash often” when bad input is received?

Yes, it is.

To imagine that a queue is building up is to imagine that we allow proceses that are at the edge of the crashability matrix (that is, workers the farthest out on the supervision tree, farthest from the crash kernel of the system) to accumulate important state.

If something has the job of accumulating a queue of messages, its sole job is probably accumulating that queue. This is a simple job, generally speaking, and so the odds of that process crashing are VERY LOW. But it could happen. Most of the memory you will ever be exposed to is non-ECC, for example, so a cosmic ray likely will cause data corruption eventually (not to mention hardware errors and random acts of malintentioned deities, also, nothing ever works right on Christmas or Tuesday). Do you want to continue on with randomly bad data or crash and recover to a KNOWN STATE?

Hint: you're never going to actually figure out what was wrong with the bad data, only that it was bad in an ambiguous way.

Since most of your Heisenbugs are going to be random acts of nature or system states that are practically impossible to replicate, crashing is by fare the more favorable option. In fact, you've really only got one option, and that is to restart *in a known state*. Would you prefer that cost you 10k LoC in some byzantine braid of exception handling code interleaved with your business logic that buys you next to nothing in terms of understanding the problem (but costs a ton in terms of development time and money), or prefer that 99% of that was already part of a framework that handles things like this by design and was totally separate from your "happy path" business logic code?

That's the tradeoff addressed in Erlang system design, and is dealt with in the general case by OTP.

So let's return to that queue accumulator process that we now know has the sole job of accumulating things to process.

Because it is simple it is more likely not to crash. Simple == more stable and easier to glance at and prove it has few, if any, bugs. Generally speaking, of course. What else might we want it to do if it is an accumulator?

Perhaps it should know some things about the state of the runtime like load and memory use -- so it can determiner whether it should have fewer or more processes doing processing jobs just then. And, perhaps most critically, it should probably know how to shed load when the queue is just unreasonably huge. That could mean telling external connections to throttle, it could be flagging an unavailable state in the system to wave clients off from making requests, or it could just silently shed load and log it but keep chugging along.

The optimal strategies are up to you, but the basic tools to implement those strategies are baked into Erlang and OTP.

But what you don't see here is any discussion about in-depth, complex, businesss logic. None. Zero. If that process' job is to track a stateful queue then that's what it does. And it protects that queue from bad things like overload. It delegates the work, though, to subordinate workers (the spawning of which might adhere to any of a handful of core strategies). Those workers are single-threaded programs that work in an isolated memory space and they do complex things, and certainly might crash. And that's totally OK in the context of a system handling a bajillion messages.

And... since the message was dispatched by the queue manager, we could decide to retain the message and react to the crash of the worker (which could very well be a Heisenbug which won't pop up on a second run) by running a retry, and if that crashes also decide to give up.

That is somwhat similar to what we see already built in to OTP. You customize this case when you need to, but you don't just build up a ton of state in a single process and then lose everything when it dies. Whenever you have started to do that is when you've started to build a crash kernel that is way too big.

You may be wondering what "crash kernel" means. Every program has a crash kernel. In the single-threaded world the entire thing is part of the crash kernel, so identifying this concept is a useless exercise. In Erlang, though, we have to figure out what part of the system's state is sort of ephemeral and can be tried again or just flat out lost without defeating the purpose of the program, and what part of the system's state is so central that if it is lost the entire system should be brought down and restarted to cope with it. The latter is the crash kernel. A primary design goal of a robust system is to actively work toward a system architecture where the crash kernel is as tiny as possible.

Some tasks are more amenable to this than others, but as you move through your Erlang life you'll find that most problems that involve automated systems are just not that critical to get right EVERY time (hardware and transmission errors tend to outnumber software ones), and that when dealing with humans you can generally rely on them to re-try stuff that didn't work -- because humans are just fantastically trainable in the skill of frantically re-clicking and retyping things (so you may want to train yourself in the skill of writing indempotent functions...).

-Craig
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: Nested Case Statements v.s. multiple functions

Richard A. O'Keefe-2
In reply to this post by dploop@163.com


On 26/09/17 3:40 PM, [hidden email] wrote:

> This problem also bothered me for a long time, so I write a parse
> transform to deal with it. To be specific, say we want to write a
> function which takes two integers and returns  the product and quotient
> of them.
>
> muldiv(First, Second) ->
>     case is_integer(First) of
>         true ->
>             case is_integer(Second) of
>                 true ->
>                     Product = First * Second,
>                     case Second =/= 0 of
>                         true ->
>                             Quotient = First div Second,
>                             {ok, {Product, Quotient}};
>                         false ->
>                             {error, "Second must not be zero!"}
>                     end;
>                 false ->
>                     {error, "Second must be an integer!"}
>             end;
>         false ->
>             {error, "First must be an integer!"}
>     end.

-module(must_be).
-export([integer/2, nonzero/2, ...]).

integer(X, _Msg) when is_integer(X) ->
     X;
integer(X, Msg) ->
     throw({must_be,integer,X,Msg}).

nonzero(X, _Msg) when X == 0 ->
     X;
nonzero(X, Msg) ->
     throw({must_be,nonzero,X,Msg}).

-module(yourapp).

muldiv(First, Second) ->
     try
         must_be:integer(First, <<"first">>),
         must_be:integer(Second, <<"second">>),
         must_be:nonzero(Second, <<"second">>),
         {ok, {First*Second, First div Second}
     catch
         throw:Reason -> {error, Reason}
     end.

No parse transform needed.

>
>
> Ugly, at least I think this "rocket" is not beautiful. Even worse, if
> requirement changed, you should modify this code on a large scale. To
> avoid this problem, you can just rewrite your code like this
>
> muldiv(First, Second) ->
>     do@([esugar_do_transform_error ||
>         case is_integer(First) of
>         true -> return(next);
>         false -> fail("First must be an integer!")
>         end,
>         case is_integer(Second) of
>         true -> return(next);
>         false -> fail("Second must be an integer!")
>         end,
>         Product = First * Second,
>         case Second =/= 0 of
>         true -> return(next);
>         false -> fail("Second must not be zero!")
>         end,
>         Quotient = First div Second,
>         return({Product, Quotient})
>     ]).

To me, this is worse than what you started with.
At least the starting version was completely standard Erlang
syntax, so I could figure it out.  Here, amongst other things,
I am flummoxed by 'return'.

For internal use, I'd rather follow the "let it crash"
approach and write

muldiv(First, Second)
   when is_integer(First), is_integer(Second), Second =/= 0 ->
     {First * Second, First div Second}.

Amongst other things, when First is *not* an integer, it is
at least as useful to know what it *is* as that it is called
First.

I do appreciate that there are cases where that can't be done.
(The must_be functions come from Quintus Prolog.  When error-
handling was introduced, it was necessary to trawl through all
the system sources making sure that errors were properly
reported *and classified* correctly, so must_be_integer
reported a 'type' error and must_be_nonzero reported a
'domain' error.  The reason for the distinction was that type
errors generally indicated that you had passed the wrong
argument, while domain errors generally indicated you had the
right argument but its value was no good for some other reason.)


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
12