Reputation: 13
I have just learned to program and feel comfortable with Java... so far. But now I have an assignment in school where we use Erlang. Which I have some problems with.
So, I have 3 simple (I think) questions:
I want to assign a boolean to a variable (or atom?) just the way you do in Java. Is this possible?
In my code below, the checkTheExistance function returns a tuple {State, boolean}. Next I will assign this to another tuple: {NextState, DoExist} is this correct? Can I put the boolean in DoExist?
Then, I want to check the boolean in a case statement. And based on the on the boolean value do something.
Hope you understand my questions. Thank you.
handle(State, {join, Pid, Channel}) ->
{NextState, DoExist} = checkTheExistance(Channel, State),
case {NextState, DoExist} of
{_,false} -> startChannel(Channel),
{_,true} -> genserver:request(Channel, {join, Pid})
end
{reply, ok, NextState};
Upvotes: 1
Views: 969
Reputation: 14042
You can assign a Boolean to a variable, but you can't "assign" an atom, an atom is already a value of type atom. Bye the way, Erlang does not have Boolean type, true and false are 2 normal atoms that are used for this purpose.
The assignment of {State, Boolean} to {NextState, DoExist} is legal. it is a good example of pattern matching. Doing this you verify that the answer is a 2 terms tuple, and you assign the variables NexState and DoExist.
The case statement is also legal, but as you ignore the term NexState in the case, you could write:
case DoExist of
false -> startChannel(Channel),
true -> genserver:request(Channel, {join, Pid})
end
Upvotes: 2
Reputation: 13154
Your question and snippet touches on a few areas at once. The explanation below is meandering and eventually gets to the answer at the end, by which time you probably already will know the answer anyway...
First: Style
We don't do camelCaseNames in Erlang because there is a concrete semantic distinction between upper case and lower case names. So your Java habit of naming a function someFunctionName
becomes some_function_name
.
Second: State alteration
Typically we would not alter any state during a check. A check is a check, a state alteration is a state alteration. The two should not mix. That is part of why we have single-assignment as a language rule -- so that when you see a variable name within a scope you are looking at a label not a pointer to a storage location. Within a single scope (generally speaking, within a function definition, a lambda, or a list comprehension -- which cannot mask outer-scope labels) a label means exactly what you said it does throughout the entire function and nothing different.
If you want to check something, check something, and give the check function exactly what it needs to do its job and (usually) nothing more:
case check_existence(Channel, State) of
exists -> something(State);
does_not_exist -> something_else(State)
end,
Or, compare the difference:
case check_existence(Channel, State) of
true -> something(State);
false -> something_else(State)
end,
What just happened here? We returned atoms. That is sort of like returning a string, but much more efficient and completely unambiguous in terms of type specifications. Actually, atoms are their own types, and they mean exactly whatever they say and nothing more within the program.
So the booleans in Erlang are true
and false
-- which are atoms. They mean exactly "true" and "false", just like has_a_fuzzy_nose
would mean precisely "has a fuzzy nose" if that is meaningful within the program. There are certain functions that accept the atoms "true" and "false", and the boolean operators accept these values, of course, but there is nothing that makes them different from any other atom in the language. In Erlang you can create many more plural logic booleans (and sometimes this is very useful) like maybe
, has
, lacks
, incomplete
, next
, end
and so on.
Third: the =
is assignment and assertion
Every line of an Erlang program is usually an assignment or an assertion (a match) on a value. The point here is to always know that you're dealing with good data, and crash if you aren't. A good example of this is wrapped values which you'll see all over the place. The typical example is any function with a side effect, which typically return either {ok, Value}
or {error, Reason}
.
The typical way to deal with this in code is to do something like:
{ok, Data} = file:read_file(FileName),
That forces an assertive match on the shape of the tuple returned, then on the atom ok
, and then assigns the contents of the read file to the variable name (label) Data
-- assuming that Data
has not yet been assigned within the current scope of execution. In this case if file:read_file/1
had returned {error, Reason}
the process would have crashed right away.
Why?
Because in the standard case we have absolutely no control over why an external resource like a file might have be unavailable -- and it would take literally thousands of lines of code to properly handle the error. So instead we just crash right there and know we did not continue on with bad data.
So about those assignments...
We have just seen an assignment, and also seen an assertion in the same moment. It would be totally possible to do something more complex like this:
Value = file:read_file(FileName),
case Value of
{ok, Data} -> do_stuff(Data);
{error, Reason} -> report_error_and_die(Reason)
end.
This would have achieved basically the same effect at a greater cost in complexity. Compare this (uselessly complex) version with the following (which is semantically the same but even more uselessly complex):
{Outcome, Value} = file:read_file(FileName),
case Outcome of
ok -> do_stuff(Value);
error -> report_error_and_die(Value)
end.
Now, remembering that the boolean values true
and false
are mere atoms, it is totally acceptable to assign the boolean return value of lists:member/2
to the variable name Result
like this:
MyList = [1,2,3,4],
Result = lists:member(2, MyList),
case Result of
true -> io:format("In the list.~n");
false -> io:format("Not in the list.~n")
end.
But more concise to do:
MyList = [1,2,3,4],
case lists:member(2, MyList) of
true -> io:format("In the list.~n");
false -> io:format("Not in the list.~n")
end.
Because we don't actually need to remember the name anywhere. There is, of course, a strong balance between use of labels as implicit documentation (good variable names convey your intent to the next reader of your code) and writing overly verbose code.
In closing...
Hopefully this explains more than it confuses. The most important thing for you to do right now is probably goof around in vim and in the erl shell to see what makes sense and what doesn't. LYSE is a very good introductory guide and reference -- I strongly recommend reading through the first few chapters to bring yourself up to speed with the sequential part of the language. Don't be scared, Erlang is actually really easy, small, and readable as a language. (Massively concurrent problems people use Erlang for, though, are by their very nature pretty huge, but that's nothing to do with the sequential language itself.)
Upvotes: 1