No tail recursive code in a try catch block? - erlang

I am reading the Erlang lesson at http://learnyousomeerlang.com/errors-and-exceptions
I don't understand this part :
The Expression in between try and of is said to be protected. This means that any kind of exception happening within that call will be caught.
And
the protected part of an exception can't be tail recursive.
[...]
By putting your recursive calls between the of and catch, you are not in a protected part and you will benefit from Last Call Optimisation.
So we can't put recursive calls in the part where the exceptions are catched ? What's the point of the try catch block then ?
And below in the page we have an example with a tail recursive function in the protected section ...
has_value(Val, Tree) ->
try has_value1(Val, Tree) of
false -> false
catch
true -> true
end.
has_value1(_, {node, 'nil'}) ->
false;
has_value1(Val, {node, {_, Val, _, _}}) ->
throw(true);
has_value1(Val, {node, {_, _, Left, Right}}) ->
has_value1(Val, Left),
has_value1(Val, Right).
Does he mean that we need to use a function to wrap tail recursive code into a function when we are in the protected part of a try catch ?

So we can't put recursive calls in the part where the exceptions are
catched ? What's the point of the try catch block then ?
A function cannot recursively call itself inside of a try; or rather, tail optimization will not happen if it does. When you use try, you have to be able to jump back to the catch block at any point down the call stack. What that means is that there has to be a call stack. If tail call optimization is used, there are no function calls because they're just loops now. There's nothing to jump back to. Thus recursion inside of a try block must truly recurse.
The point is the same as exceptions in most languages. The fact that you cannot directly recurse is a bit of an annoyance, but certainly does not remove the utility of exception handling, because:
Does he mean that we need to use a function to wrap tail recursive
code into a function when we are in the protected part of a try catch
?
Yes. All it takes is one extra function and you can use try just fine, and still gain the benefits of TCO. Example:
% No TCO
func() ->
try
func()
catch _ ->
ok
end.
% TCO
func() ->
try
helper()
catch _ ->
ok
end.
helper() -> helper().
I'm not sure if there's an easy way to determine if you're accidentally recursing when you expect TCO to happen. You probably just have to be vigilant when using try.

If you want to have tail-call to be optimized, that call must be outside of try-catching clause. You can use construction
your_fun(...) ->
...
try ... of <--- make notice of `of`
... ->
some_call(...)
catch
...
end.
or just make that call after try clause.
In your code that call has_value1(Val, Right). is optimized because it's the last call in the function. It doesn't matter if it is called inside try block in this case. And exception is used only to provide early exit from this function and simple handling of the result.
It could be rewritten without exceptions but using manual stack handling:
has_value(Val, Tree) ->
has_value(Val, [Tree]).
has_value1(_, []) ->
false;
has_value1(Val, [{node, 'nil'} | Stack]) ->
has_value1(Val, Stack);
has_value1(Val, [{node, {_, Val, _, _}} | _]) ->
true;
has_value1(Val, [{node, {_, _, Left, Right}} | Stack]) ->
has_value1(Val, [Left, Right | Stack]).

Related

How can I get Dialyzer to accept a call to a function that intentionally throws?

I have a function that intentionally throws when its first argument is the atom throw.
A simplified version of this code is:
-module(sample).
-export([main/1, throw_or_ok/1]).
main(_Args) ->
throw_or_ok(throw).
throw_or_ok(Action) ->
case Action of
throw -> throw("throwing");
ok -> ok
end.
Dialyzer errors on the call to throw_or_ok:
sample.erl:7: The call sample:throw_or_ok
('throw') will never return since it differs in the 1st argument from the success typing arguments:
('ok')
Adding specs doesn't help, the error message is the same:
-module(sample).
-export([main/1, throw_or_ok/1]).
-spec main(_) -> no_return().
main(_Args) ->
throw_or_ok(throw).
-spec throw_or_ok(throw) -> no_return(); (ok) -> ok.
throw_or_ok(Action) ->
case Action of
throw -> throw("throwing");
ok -> ok
end.
How can I get Dialyzer to accept calls to throw_or_ok/1 that are guaranteed to throw?
Unfortunately there is currently no clean way to mark this as acceptable for Dialyzer via specs.
Perhaps you can use an ignore warning annotation, however.
Looks like if will put throw it will never return, if will put ok the pattern will never match with throw. See topic with similar issue. The logic of main/1 need to change, eg:
main(Args) ->
MaybeOk = case Args of
0 -> throw;
_ -> ok
end,
throw_or_ok(MaybeOk).
OR
main(_Args) ->
throw_or_ok(_Args).

Erlang How to mix try catch with if

I'm very new in Erlang.
I want to make function to check ban word.
But I got syntax errors..
How to use try catch with if else statement?
check_banword(Word, BlackWord) ->
try
Res = string:rstr(Word, BlackWord),
if Res > 0 ->
true;
true ->
false
catch
false
end.
Two problems in the code:
Missing end after if
Catch syntax is incorrect, catch matches exceptions to values, you cannot have just a value there.
The code with changes enabling it to compile looks like
check_banword(Word, BlackWord) ->
try
Res = string:rstr(Word, BlackWord),
if
Res > 0 ->
true;
true ->
false
end
catch
_ -> false
end.
In this case, you don't actually need the if, you can use try with patterns and guards. When used in this way, try looks like a case expression with a catch section at the end. (Note that only the part between try and of is "protected" by the catch - in this case, it doesn't make any difference because the rest of the code cannot raise an error.)
Also note that you need to specify what type of exception you want to catch, one of error, exit and throw. If you don't specify the type of exception, it defaults to throw - and that's not what you want here, since string:rstr never throws anything, it can only raise errors if the arguments are of an incorrect type.
So that would be:
check_banword(Word, BlackWord) ->
try string:rstr(Word, BlackWord) of
Res when Res > 0 ->
true;
_ ->
false
catch
error:_ -> false
end.
And there is a wider question: should you actually use try here? If an error occurs in this function, it means that the arguments were not strings, and that is the caller's responsibility. Since there isn't anything that this function can do to fix the problem, I'd say that it shouldn't catch the error but let it propagate to the caller. (This is known as Erlang's "let it crash" philosophy.)

What's the point of meck:validate?

As a newcomer to meck, I've been putting together a test that shows the various features. I cannot, however, understand why a developer might call meck:validate. Here's my example:
-module(meck_demo).
-include_lib("eunit/include/eunit.hrl").
validate_is_of_limited_use_test_() ->
{ foreach, fun setup_mock/0, fun cleanup_mock/1,
[fun validate_does_not_fail_if_a_function_is_not_called/0,
fun validate_does_not_fail_if_a_function_is_called_with_wrong_arity/0,
fun validate_does_not_fail_if_an_undefined_function_is_called/0,
fun validate_does_fail_if_a_function_was_called_with_wrong_argument_types/0,
fun validate_does_fail_if_expectation_throws_an_unexpected_exception/0 ]}.
validate_does_not_fail_if_a_function_is_not_called() ->
meck:expect(womble, name, fun() -> "Wellington" end),
?assert(meck:validate(womble)).
validate_does_not_fail_if_a_function_is_called_with_wrong_arity() ->
meck:expect(womble, name, fun() -> "Madame Cholet" end),
?assertError(undef, womble:name(unexpected_arg)),
?assert(meck:validate(womble)).
validate_does_not_fail_if_an_undefined_function_is_called() ->
?assertError(undef, womble:fly()),
?assert(meck:validate(womble)).
validate_does_fail_if_a_function_was_called_with_wrong_argument_types() ->
meck:expect(womble, jump, fun(Height) when Height < 1 ->
ok
end),
?assertError(function_clause, womble:jump(999)),
?assertNot(meck:validate(womble)).
validate_does_fail_if_expectation_throws_an_unexpected_exception() ->
meck:expect(womble, jump, fun(Height) -> 42 = Height end),
?assertError({badmatch, 999}, womble:jump(999)),
?assertNot(meck:validate(womble)).
setup_mock() ->
meck:new(womble, [non_strict]).
cleanup_mock(_SetupResult) ->
meck:unload(womble).
What am I missing?
-- Updated to reflect the cases that Adam explains can be caught
You managed to hit just about every case not covered by validate (added better documentation in 10c5063).
Validate can detect:
When a function was called with the wrong argument types (function_clause)
When an exception was thrown
When an exception was thrown and expected (via meck:exception/2), which still results in true being return from meck:validate/1
Validate cannot detect:
When you didn't call a function
When you called a function with the wrong number of arguments
If you called an undefined function
The reason it cannot detect these cases is because of how Meck is implemented. Meck replaces the module with a mock and a process that maintains the mock. Everything Meck gets goes through that mock module. Meck does not insert itself at the caller level (i.e. in your module or in your test case), so it cannot know that you failed to call a module. All of the failures in your test case above never reaches the mock module in the first place.

spawn_link is starting an infinite loop, but does not return?

I have an init function which I call start_server:
start_server() ->
spawn_link(game_loop(0,0)).
The purpose is to start a new process that begin to loop and waiting for someone to send a message there:
game_loop(X,Y) ->
receive
{move, left} ->
tell_client(X+1,Y),
game_loop(X+1,Y);
{move, right} ->
tell_client(X-1,Y),
game_loop(X-1,Y)
end.
My thaught was that start_server would return the Pid so that I could write something like this in the Erlang terminal:
> Server = server:start_server().
And then use the variable Server to handle the servers via functions like:
move_left(Pid) ->
Pid ! {move, left}.
But that does not work since start_server() never returns, why is that?
The function spawn_link/1 takes a function as an argument. But in your code you dont pass a function into it:
start_server() ->
spawn_link(game_loop(0,0)).
That sample means that the function game_loop/2 will be called first and after it returns spawn_link/1 will be called with an argument which is a result of calling of game_loop/2. But your function game_loop/2 implements infinite loop so it will never returns and so spawn_link/1 will never be called. If we even assume that game_loop/2 returns it must return a function to call spawn_link/1 properly, otherwise an exception will rise.
To do what you want you should pass game_loop/2 as a function into spawn_link/1:
start_server() ->
spawn_link(fun () -> game_loop(0,0) end).

Unintentionally intercepting Mnesia's transactional retries with try/catch results in all kinds of weirdness

So, I was having all kinds of trouble with CRUD operations on sets of records in one transaction. It lead me to post 2 questions here, Trouble and MoreTrouble. However, I think that both those issues where created by the following: Within my transactions, I enclosed my mnesia:writes, reads, etc. in try/catch blocks that caught everything including mnesia's aborted transactions as part of its deadlock avoidance algorithm. I.e.,
insert(Key, Value) ->
F =
fun() ->
case sc_store:lookup(Key) of
{ok, _Value} -> sc_store:replace(Key, Value);
{error, not_found} -> sc_store:insert(Key,Value)
end
end,
try
case mnesia:transaction(F) of
{atomic, Result} -> Result;
{aborted, Reason} -> ...
end
catch
Error:Reason -> ...
end
end
sc:lookup/1, for example, looked like this:
lookup(Key) ->
try
case mnesia:read(key_to_value, Key) of
[#key_to_value{type = Type, scope = Scope, value = Value}] ->
{ok, {Value, Type, Scope}};
[] ->
{error, not_found}
end
catch
_Err:Reason -> {error, Reason}
end.
I think I must have been "intercepting" / catching mnesia's dead-lock avoidance algorithm instead of letting it retry as designed.
Is that possible? If so, its a (&^& of a gotch-a for a newbee like me. If not, any ideas why this code produced so many problems for me, but removing the try/catch from the mnesia:read, etc. functions cleared up all of my problems?
Yes, I'm not sure if that's properly documented anywhere, but you should not mask out the exceptions in mnesia operations. If you do that, it looks to mnesia like your transaction fun worked as intended, even though some operations actually didn't work at all.

Resources