[erlang-questions] feedback please

zxq9 zxq9@REDACTED
Thu Sep 24 06:44:55 CEST 2015


On Wednesday 23 September 2015 22:30:33 Roelof Wobben wrote:
> Hello,
> 
> I did chapter 4 of Programming Erlang  which is about pattern matching.
> 
> So as a challenge I tried to make a little cash on hands programm.
> 
> Here is the code :
> 
> -module(balance).
> 
> -export([get_balance/1]).
> 
> get_balance(Balance)  ->
>      io:format("~n~n"),
>      io:format("Your account balance is $~w.~n",  [Balance]),
>      
>      Entered_action  =  io:get_line("Which Action do you need (w)ithdraw/(d)eposit? "),
>      Action  =  string:strip(Entered_action,  right,  $\n),  
>      
>      Entered_amount  =  io:get_line("Which Amount do you want to withdraw/deposit? "),
>      Amount2  =  string:to_integer(string:strip(Entered_amount,  right,  $\n)),
>      Amount  =  element(1,Amount2),
> 
>      get_balance(Balance,  {Action,  Amount}).
> 
> get_balance(0,{"w",_  })  ->
>      io:format("You have no funds to withdraw"),  
>      get_balance(0);  
>      
> get_balance(Balance,  {"w",  Amount})  when  Amount   >  Balance  ->
>      io:format("You cannot withdraw more then you have"),  
>      get_balance(Balance);  
> 
> get_balance(Balance,  {"w",  Amount})  ->  
>      get_balance(Balance  -  Amount);
> get_balance(Balance, {"d", Amount}) -> get_balance(Balance + Amount);

I'm assuming the last clause got cut off; maybe something like:

get_balance(Balance, BadInput) ->
    ok = io:format("CUT THAT OUT! \"~tp\" is not a valid option!~n",
                   [BadInput]),
    get_balance(Balance).

> Are there things I could do better without processes or OTP because they 
> are still not explained.

On the point of pattern matching, your program makes natural use of of it. I think it is clear you basically understand how pattern matching works in function heads -- and that is the point of the chapter. You made good use of a guard, also, which is sort of a natural extension of pattern matching (both in function heads and case statements).

I'm sure we could spend a week blathering about user interface issues, how to structure functions around UI elements, events, etc., but that's not the point. There is a whole school of thought on how to structure functional UI code, but talking about that distracts from the point of pattern matching and guards.

There are a *lot* of opportunities to use pattern matching in Erlang. You will realize this the more you write little toy programs like the one above for yourself, and especially when as you write, hack around on and refactor programs you care about. It is super useful and something you will sorely miss in languages that lack it.

Something you may notice in other people's code is conversion of external input to internal atoms that represent something about the input. This can make programs easier to read, and incidentally can make them faster (though usually not noticeably so). For example, you accept two types of user actions, Withdrawl and Deposit. The user can input "w" or "d". We could broaden the variety of input that is acceptable, make the pattern-matching part of the code more readable (more self-documenting -- important if you have a huge menu of options), and keep the "bad external input" case away from the get_balance/2 processing function:


-module(roelof1).
-export([get_balance/1]).

get_balance(Balance)  ->
    io:format("~n~n"),
    io:format("Your account balance is $~w.~n",  [Balance]),
    Action = ask_action(),
    Amount = ask_amount(),
    get_balance(Balance,  {Action,  Amount}).

get_balance(0, {withdrawl, _Amount}) ->
    ok = io:format("Hey! You're broke!~n"),
    get_balance(0);
get_balance(Balance, {withdrawl, Amount}) when Amount > Balance ->
    ok = io:format("You cannot withdraw more than you have~n"),
    get_balance(Balance);
get_balance(Balance, {withdrawl, Amount}) ->
    get_balance(Balance - Amount);
get_balance(Balance, {deposit, Amount}) ->
    get_balance(Balance + Amount).

ask_action() ->
    Input = io:get_line("Which action do you need? (W)ithdrawl/(D)eposit? "),
    case Input of
        [$w | _] -> withdrawl;
        [$W | _] -> withdrawl;
        [$d | _] -> deposit;
        [$D | _] -> deposit;
        BadInput ->
            ok = io:format("You can't seem to '~tp' today...~n"
                           "Let's try that again...~n",
                           [BadInput]),
            ask_action()
    end.

ask_amount() ->
    Input = io:get_line("How much? "),
    case scrub_amount(Input) of
        {ok, Number} ->
            Number;
        {error, bad_input} ->
            ok = io:format("Hrm... '~tp' does not seem to be a number~n"
                           "Now, one more time...~n",
                           [Input]),
            ask_amount()
    end.

scrub_amount(Input) ->
    case string:to_integer(Input) of
        {error, no_integer} -> {error, bad_input};
        {Int, _}            -> {ok, Int}
    end.


There are still plenty of odd cases up there above (What about {dollars, pennies, fractional} values? What if the user wants to withdraw $1.50? How to gracefully exit? etc.) -- but the point is not to present a case for fully-massaged external input, my point is that pattern matching is used everywhere and makes it very natural to use symbolic references to certain types of input (user commands, in this case) as well as present a lot of chances to break your large functions into smaller, much more narrowly focused functions.

Pattern matching is GREAT! Its really nice to see that you've got a grip on this. Anyway, if you are able to read the code above without your head exploding then it is time to move on to the next chapter in your book. I think you've got pattern matching covered.

Keep going!

-Craig



More information about the erlang-questions mailing list