[erlang-questions] Must and May convention

Joe Armstrong <>
Thu Sep 28 13:36:30 CEST 2017


On Thu, Sep 28, 2017 at 8:14 AM, zxq9 <> wrote:
> On 2017年09月28日 木曜日 05:43:33 you wrote:
>> I dont think its been mentioned, elixir does this with the ! postfix.
>>
>> {ok, Bin} | {error, Reason} = File:read("my_file")
>>
>> Bin | throw/1 = File:read!("my_file")
>>
>>
>> Exactly as you said Mr. Armstrong, the former is more clear off the bat but the latter gives you a nice error (with the filename!).
>>
>> Which do I prefer?  It seems both are useful in certain cases, and one should not replace the other as the absolute truth. If
>> an absolute truth were to be arrived at, then I would vote for the option to have both! A way to call any STDLIB function and have it return a tuple, or throw/exception.
>
> Elixir may do that, but I think adding a magical glyph to the syntax is almost universally a mistake. Not because this one case is right or wrong, but because the thought process underlying it causes languages to eventually suffocate themselves -- as you may have begun noticing... And no, adding a bang at the end of a function call is NOT the same as naming two separate functions, unless bangs are acceptable characters to unclude in function names (are they?).
>
> If they are, some fun names might result:
>
>   launch!the!missiles!(BadGuys)
>
>   clean_your_room!dammit!(Kids)
>
> If they are legal characters, though, then what you are referring to is a mere coding convention -- arguably of less utility that matching wrapped values VS receiving naked ones, which has a concrete runtime effect (and is understood by the type system).
>
> If they are not legal characters and are actually a part of the core language (separate from the type system language) then... that's something I think should be contemplated for a HUGE amount of time before dropping into the language (things like that should be argued for years on end before just being picked as some new idea -- unless it is a research language, then, meh).
>
> That said, the point I was making is not that we should have both because everything is all the same and all functions should be treated equally and we really really need more diversity in the way we handle function call returns. The point I was making is that some functions are CLEARLY PURE and that is just an innate part of their nature. Other functions have side effects and, systems using runtime prayers/monadic returns aside, there is just no way to make a side effecty function pure.
>
> On that note, in a practical sense, when we return {Reply, NewState} in a gen_server we are sort of doing what IO monads do for Haskell. There is NO GUARANTEE from the perspective of the called function that the Reply value will actually be returned to the caller. That is obviously the intent, but it is by no means clear. This property also makes it very convenient to hook such functions up to a property tester. On the other hand, the tuple returned itself can be viewed as a naked value.
>
> Returning naked values mandates purity or total crashability of the system. Nothing in between. Unless you want to play "confuse the new guy".
>
> When we have side effects, such as file operations, there is no difference between calling
>
>   {ok, Bin} = file:read_file(Path)
>
> and
>
>   Bin = file!:read_file(Path)
>
> That's a frivolous reason to add a glyph to the language. The assertion at the time of return already crashes on an error.

Actually I half this but would prefer Bin=file:read_file!(P) - the problem
is that ! is a weak typographic character - so it's easy to miss, and ! means
send so we might (or rather will) confuse beginners.

I guess we could say

     Bin = MUST file:read_file(Path)

Easy to implement and it stands out nicely so you can see it from a
long way off.
Loosely scanning many lines of code and seeing MUST would be nice,
it could be used to signify which parts of the code must be correct.

I actually quite liked the Elixir pipe operator - so I changed the
Erlang parser and messed with this a bit.

So now I can write

test2(F) ->
    F  |> file:read_file()
        |> ok()
        |> tokenize()
        |> parse().

Instead of

test2(F) ->
    {ok, B} = file:read_file(),
    T = tokenize(B),
    parse(T).

Where:

ok({ok,B}) ->
    B.

I can't make my mind up about this - the version with pipes is slightly longer
but seems very readable to me. Trouble is, if it crashes due to a non existent
file, I can't see which file it is ...

/Joe






>
> And no, you don't "need both". That's WHY the return value is matched in the first place -- to leave it up to the caller how they want to handle a surprise {error, Reason}.
>
> Knowing that you are going to get a wrapped value is exactly how you know you are dealing with side effects somewhere, rendering both the return value issue moot and the question of whether the thing you're calling is pure or has a side effect.
>
> -Craig
> _______________________________________________
> erlang-questions mailing list
> 
> http://erlang.org/mailman/listinfo/erlang-questions


More information about the erlang-questions mailing list