[erlang-questions] Some functions must return values other may do so...

Loïc Hoguin essen@REDACTED
Sun Aug 16 15:57:39 CEST 2015

On 08/16/2015 03:35 PM, Fred Hebert wrote:
> On 08/16, Jesper Louis Andersen wrote:
>> On Sat, Aug 15, 2015 at 4:56 PM, Joe Armstrong <erlang@REDACTED> wrote:
>>> Rule1: Functions which return {ok,X} | {error,W} are called may_<name>
>>> Rule2: Functions which return a value or raise an exception are called
>>> must_<name>
>> val zip : 'a t * 'b t -> ('a * 'b) t option
>> val zip_exn : 'a t * 'b t -> ('a * 'b) t
>> I think it is a good idea. Knowing off-hand if a function has effects is
>> always a good thing. And the caller knows immediately when reading the
>> code
>> that this may fail in unexpected ways if the invariants are broken.
> The thing that weirds me out about that approach is that it reminds me a
> lot of hungarian notation.
> Whereas scheme or ruby would follow functions with ? (for booleans) or !
> (for destructive updates), Hungarian notation would have you encode the
> return types in other fun ways:
> - arru8NumberList : variable is an array of unsigned 8-bit integers
>   ("arru8");
> - strName : Variable represents a string ("str") containing the name,
>   but does not specify how that string is implemented.
> And then you add:
> must_zip : returns the result or fails
> may_zip:  returns {ok, Result} or {error, Reason}
> both of these look suspiciously like encoding your types in the names
> because the language does not offer enough support (without Dialyzer).
> I can't also imagine it a fun time to reimplement everything twice to
> offer changing semantics to please any given user.
> - raise exceptions for unforeseen errors that must be the caller or the
>   developer's fault
> - return {ok, _} and {error, _} for expected errors that naturally flow
>   from usage and that the user could reasonably be expected to handle.
> - encode these in the type system (-spec f() -> {ok, _} | {error, _} |
>   no_return(). , where no_return() points to manually raised exceptions.
> That's my take on it anyway.

It's not just about what you return!

Imagine you are validating input.

There are two ways to go with validating input:

* You detect all errors and display a nice message to the user
* You crash on the first error and stop there (no need to do more if you 
do machine to machine)

The functions do the same thing, validate input, take the same 
arguments, and return the validating input in a usable format.

Their behavior differ only in how they handle errors (and therefore 
their return value). The first one will do what it can to provide you 
with all information you need to format a nice message to the nice user; 
the second one will just crash.

You could make the immediate crash behavior configurable through an Opts 
argument, but then your function needs an Opts argument which it may not 
have had beforehand. It's also not as obvious to the user as a different 
function would be. And it's much easier to standardize on a prefix or a 
suffix than on option formats and names.

And you don't have to reimplement everything twice! It's just the 
*interface* that has two functions. You can have a special argument for 
this (or a special value in place of your error accumulator) inside the 
private function that implements the input validation.

Loïc Hoguin
Author of The Erlanger Playbook,
A book about software development using Erlang

More information about the erlang-questions mailing list