1. 7

    Erlang making this hard is the biggest reason I didn’t continue to learn Erlang. I don’t understand how the “just crash” philosophy and actually returning useful error messages are supposed to intersect. I have just assumed since Erlang was made for telecom infrastructure, where I assume it’s reasonable to drop bad input rather than return errors, it just doesn’t have an ergonomic way to return errors. At least based on my experience with switches and routers, which tend to drop bad packets with only a few exceptions.

    I’d be happy to learn I’m wrong though.

    On the flip side, Swift guard statements make this pattern delightfully easy. In particular, I like that you can do a guard let and the assignment will occur in the outer scope, as opposed to if let which makes the binding local to the body to the if.

    For example:

    guard let value = possiblyReturnsNil() else {
        // handle error
        return
    }
    
    doSomething(value) // valid
    

    Notice the else after the guarded expression, that hints how the scoping works. So guard let isn’t quite the same as what you might expect unless let / if not let to be. Aces.

    1. 5

      The way you tackle these things in Erlang is by tagging the return values. If you really, really need to return early, you can always erlang:throw/1, which does pretty much what return does in javascript.

      Admittedly, you can end up with multiply-indented case statements in Erlang, too, but there are ways of dealing with that (depending on your style) that don’t involve non-local returns.

      1. 2

        I’m familiar with return tagging. The nested case statements are what bother me. I appreciate the link but that strategy is so over the top and verbose that I absolutely don’t want to do anything like it for generalized input guarding. Throw might be what I want but it seems wrong, maybe because of how I’m used to using exceptions in other languages, maybe not. Like what if your caller doesn’t expect you to throw, and it isn’t your code? You’d have to wrap everything in a catching function. It feels like way more work than it should be just to have access to early returns.

        1. 5

          I use the following construct in my code:

          -spec fold(t(A,B), [fun((A) -> t(A,B))]) -> t(A,B).
          fold(R, []) when ?is_result(R) -> R;
          fold({error,E}, _) -> {error, E};
          fold({ok, V0}, [Fun|Rest]) -> fold(Fun(V0), Rest);
          fold(Other, []) -> error({badarg, Other});
          fold(_, Other) -> error({badarg, Other}).
          

          If you squint, it’s an early-exit chain of >>= (monadic binds) on the squintly-typed Either type.

          Then, you can use the following pattern:

          squinty_either:fold({ok, InitVal}, 
          [fun(V) -> {ok, x(V)} end,  %% x is total and pure
          fun(V) -> 
            case some_pred(V) of   %% case statement inlined, should be sep. fun.
              true -> {ok, z(V)};
              false -> {error, {V, not_valid}}
            end
          end,
          fun(V) -> ... ]
          

          Any fun that returns {error, E} will cause the entire chain to exit with that tuple. OTOH, returns of {ok, V} will pass on the bare V to the next element in the chain. This means you only code for the relevant path. You can then deconstruct the values in function heads to further reduce case-yness.

          Regarding the ‘unknown code crashing your process’ problem: yes, I hear you. There is no foolproof solution to this: sometimes you do have to explicitly try-catch, sometimes your running process doesn’t care and can just crash, because the supervision tree is built in such a way that it doesn’t matter.

          1. 2

            I like this pattern overall, but I still wish it was more ergonomic. I believe the |> operator in Elixir exists to essentially do this?

            1. 2

              Yeah, ergonomics isn’t Erlang’s strong suit. I does help if all your functions have a uniform return type, so you can get by with just referencing them:

              fold({ok, N}, [ fun add_one/1, fun add_two/1, fun reject_odd_numbers/1 ]).
              

              where all the above functions are :: number() -> {ok, number() | {error, any()}

              In Elixir, the |> operator solves half of the issue (chaining), and the with syntax solves the other part of the issue (logic/dispatch based on previous return value). Yet, there is no succinct way of combining them, i.e. implementing something like Haskell’s >>=.

              *edited example function name

              1. 2

                The fold strategy reminds me of pipeline from Joyent’s vasync library. Node has a callback-based runtime that totally eradicates anything resembling a call stack whenever you have to do IO, so using a library that attaches context to function calls makes sense. Dynamically executing functions just to get a syntax you like seems silly though. What about generating macro code into a header?

                1. 1

                  It’s been done: https://github.com/rabbitmq/erlando The issue with marco- and parse-transform-based Erlang libraries is that they don’t ‘stick’ in the ecosystem. My guess is that developers are too used to 1:1 mapping of code to bytecode (for reasons of debuggability). Also, parse transforms can be brittle/untestable. Basho’s lager is pretty much the only parse_transform that I’ve seen embraced extensively in the wild.

                  From a different angle, there’s nothing dramatically silly about dynamically executing functions in Erlang. There are tons of places all around OTP that do this: see the {Mod,Fun,Args} interfaces to Supervisors and gen_servers, dynamic callbacks in gen_event, mnesia transactions, etc. fun(Blah) -> expressions even get compiled to ‘named’ functions during lambda lifting, and they are very efficient, unless of course huge environment captures are involved. Erlang is in fact very introspective and dynamic – I’d go as far as to say that it is almost a lisp, at heart. I’m certain Robert Virding played no small part in making it so.

                  1. 1

                    From a different angle, there’s nothing dramatically silly about dynamically executing functions in Erlang.

                    Yes that’s true. I was thinking about it purely from force of habit—I normally use C++. I just traced a performance issue in some code using std::function down to an allocation in libstdc++ that only happens if the function is a lambda with captures. 8 bytes made all the difference. So you can see what I’m used to thinking about. ¯\_(ツ)_/¯

          2. 4

            The nested case statements are what bother me.

            I’ve generally addressed this with two solutions: 1) toss the nested handling into a function call 2) case on a tuple that has everything that can be evaluated in it and case on the dot product of options. 2 obviously only works if you nested cases don’t depend on each other.

            1. 3

              Elixir addresses nested cases with the with macro.

              1. 2

                I should give Elixir a spin, given how much I love Ruby.

              2. 1

                I’ve done the nested handling as function calls. It annoys me because it splits up all the code in a Node.js kind of way. But it’s decently ergonomic as the function names end up as comments for the early exit condition, and I think early exit conditions should be commented unless they’re really truly obvious.

                I haven’t heard of doing a single case over all the inputs before. That’s a fantastic idea! My first thought is I usually order my checks and returns to avoid writing out the Cartesian product of their conditions in if/else statements, so this strategy could be cumbersome. But pattern matching, wildcard _ in particular, should provide opportunities to merge cases. I’ll definitely have to try it out!

                Thinking about it also makes me curious how aggressively Erlang can optimize pattern matching. For example, suppose you have 4 independent options, one of which is expensive to compute, and there is only one valid case. If you just case over all 4 options anyway, will the expensive one only be computed in some cases? Or will all options be fully evaluated and bound before the pattern matching starts? Can Erlang detect some functions have no side effect? Of course you can manually optimize this pretty easily, but I’m still interested in seeing what Erlang can do there.

        1. 1

          Is there a reason one cannot parse a JS string into a simple-ish JavaScript AST object, and vice versa? It seems like if the parser were written in a simple / reasonable way, one could also extend it with a macro system.

          1. 1

            I don’t have any experience with them, but implementations of ASTs for JavaScript exist… https://astexplorer.net/ http://jointjs.com/demos/javascript-ast … also macros! http://sweetjs.org/

          1. 1

            I’ve been alternately impressed and frustrated by Chrome’s ability to set breakpoints in CoffeeScript and/or JSX… Here’s hoping they’ll improve breakpoint setting as well!

            1. 3

              … but the one thing I keep coming back to, that I believe has enduring value in almost all situations, is the audition project:

              The most significant shift we’ve made is requiring every final candidate to work with us for three to eight weeks on a contract basis.

              I don’t doubt it’s effectiveness at generating a realistic work sample, but I can’t imagine ageeing to a multi-week project as part of an interview. Even if I were between jobs, just one of these projects would uncomfortably limit my time for generating competing offers. Maybe I’m an outlier, but I have the feeling this would really shrink the application pool, retraining those who are more desperate or star-struck.

              1. 1

                Shrinking the application pool may be acceptable or even desirable, if they’re telling themselves that they’re eliminating the not-best applicants…

              1. 15

                I would love for this to be an urban fantasy animated film. Google is the villain, building up its wage-fixing cartel, when it is beset by teams of magical unicorns who smash in and rescue the hostage engineers.

                “Muah ha ha, you’ll need Steve Jobs' personal permission to leave my castle! *crash* No! Guards, guards! Curse you, unicorns!”

                1. 4

                  and Google is run by literal giants!