[erlang-questions] Orelse and andalso as short-hand for case

zxq9@REDACTED zxq9@REDACTED
Fri Jul 27 01:52:59 CEST 2018


For whatever reason, Richard's responses never made it into the archive.
I believe they are worthy of preservation.

-Craig

On 2018年7月25日水曜日 15時00分03秒 JST Richard O'Keefe wrote:
> Pierre Fenoli does not "understand the strong answers".
> I imagine that other people have had the experience of having to maintain
> someone else's code, and after a long struggle to disentangle unduly
> "clever"
> code found themselves shouting "Why did this offspring of an incestuous
> union between two male naked mole-rats not remove xer hands from xer
> genitals long enough to write down what xie actually meant and NOT WASTE
> MY RAPIDLY DWINDLING TIME?"
> 
> I am still nowhere near as good a programmer as I would like to be.
> I need to make my own code clearer.
> "What is hateful to you, do not do to others."
> If anyone catches me abusing short-circuit operations, my apologies
> in advance.
> 
> The andalso and orelse operators are defined in section 8.14
> http://erlang.org/doc/reference_manual/expressions.html#short-circuit-expressions
> of the Erlang reference manual.  It is clear from that that
> these operators originally required their right operand to yield
> true or false, and were only changed in order to allow code like
> 
> 
> ismember(X, [Y|Ys]) -> X == Y orelse ismember(X, Ys);
> ismember(_, []    ) -> false.
> 
> to be tail recursive.
> 
> It would be really nice if the Dialyzer would warn about misuses of
> these operators.  What about the 'legit example' we were offered?
> It is a blemish in an otherwise pleasant file in an impressive and
> useful project.  The only occurrence of 'andalso' in that 628 line
> file is a good example of when NOT to use 'andalso'.
> 
> task_by_pid(Pid, #state{tasks = Tasks}) ->
>     F = fun (_, #{worker_pid := WorkerPid}=Task, Acc) ->
>                 Pid =:= WorkerPid
>                     andalso throw({'task', Task}),
>                 Acc
>         end,
>     try maps:fold(F, 'undefined', Tasks)
>     catch 'throw':{'task', Task} -> Task
>     end.
> 
> which is 9 lines.  It garden-paths the reader into a
> sort of double negation.  The goal of F is *not* to return Acc,
> but to find a Task, and the throw is *not* there to report
> failure, but success.
> 
> This can be written, not only without 'andalso',
> but also without 'throw' and 'catch', as
> 
> task_by_id(Pid, #state{tasks = Tasks}) ->
>     task_with_matching_worker_pid(Pid, Tasks).
> 
> task_with_matching_worker_pid(Pid, [Task = #{worker_pid := Pid}|_]) ->
>     Task;
> task_with_matching_worker_pid(Pid, [_|Tasks]) ->
>     task_with_matching_worker_pid(Pid, Tasks);
> task_with_matching_worker_pid(_, []) ->
>     undefined.
> 
> which is also 9 lines.  To me this is a fairly obvious linear
> search for a matching list element, which leaves me free to
> think about things like whether a map from worker Pids to Tasks
> might not be a better data structure, and also lets me muse,
> "hmm, isn't there already a library function to look for the
> first matching list element?  Oh yeah."
> 
> task_by_pid(Pid, #state{tasks = Tasks}) ->
>     case lists:search(fun (#{worker_pid := Id}) -> Id =:= Pid end, Tasks)
>       of {value, Task} -> Task
>        ; false         -> undefined
>     end.
> 
> which is now 5 lines, and is how I'd have written it in the first place.
> 
> I honestly wasn't expecting this.  Given that this was Kazoo, I
> was expecting to have to mumble and shuffle and say "well, maybe
> OCCASIONALLY you can get away with it in otherwise really good
> code", but the abuse of 'andalso' DID turn out to be a 'code smell'
> pointing to contorted code that needed rewriting.
> 
> If, as would not be surprising, I have misunderstood thpe original
> code, that just proves my point that it was contorted.
> 





More information about the erlang-questions mailing list