4 ms·
I don't understand the purpose of `with`. If it's to help people deal with deeply nested or byzantine case-clause structures, then that seems weird to me. Isn
by im_down_w_otp 11y ago
I don't understand the purpose of `with`. If it's to help people deal with deeply nested or byzantine case-clause structures, then that seems weird to me.
Isn't the way to deal with that problem is to hoist those clauses into functions with different pattern signatures and rely on function-head matching?
Everything else looks awesome. I have to say, somewhat selfishly, that my favorite part of Elixir is actually the pressure it's putting on the Erlang community to up its game and care more about developer experience and enablement through better tooling, etc.
- tuvistavie 11y agoI would say the usual way to do it is as you said, to use functions and pattern matching, but there are some cases where it makes sense to have multiple things that could failed grouped in the same function, which is where `with` helps to keep things clean. For example, If I want to find a header from a request and then to extract some info from it, I think it makes more sense to keep everything in a single function.
- im_down_w_otp 11y agoHmmm. I tend to think of finding something in a set and extracting things from it as two different logically distinct things, and thus two different functions. Or some superset function that composes the two subset functions beneath it if I want the convenience (in the case that I'm unlikely for my particular use case to not do those tasks independently almost ever). Though I can certainly see your point of view. However, I'm not sure what you mean by "there are some cases where it makes sense to have multiple things that could failed grouped in the same function". I'm almost certainly misinterpreting what you're saying, but that sounds like setting up a situation where it's harder to know exactly what failed because you're grouping failure modes together, which now means you need to have more understanding/context of your application and the local execution environment surrounding the point of failure to reason about what actually happened. Almost always, except cases where I can't usefully trap a somewhat questionable lower-level API, I only ever, ever program the "happy path" in Elixir and Erlang. If I find myself doing anything that even remotely looks like active defensive programming, then I catch myself, step back from my local problem, and realize I need to rethink the flow control of my application and probably either expand or deepen my supervision tree. I always tell people, "At this point Erlang feels less like a programming language to me and almost more like a mostly declarative DSL for creating distributed systems." :-) That's truly one of the greatest things about OTP Supervisors in my opinion. When used diligently they really let you cleanly separate fault-tolerance concerns from your application code, and when doing code reviews they give you an immediately obvious place to start looking for leaky abstractions and tightly coupled subsystems.
- kiliancs 11y agoA scenario that made the need for 'with' clear to me: You have a function that passes a subject through several functions using the pipe: subject |> fn1 |> fn2 |> fn3 But now you want to return an error monad if everything went wrong. You could do the following: with {:ok, v1} <- f1(subject), {:ok, v2} <- f2(v1), do: f3(v2)
- erydo 11y agoThe `with` statement seems similar to Haskell's `do` notation for monads, where the monad(ish) here is pattern matching. I'm new to Elixir but I've definitely run into the "byzantine case-clause" issue and it can be very annoying. My case was very similar to the example: pulling something from a form, validating the format, validating it in the database, and returning something. All failable, single-purpose functions in sequence to narrow down the result. I grappled between using a monad macro library[0] to simplify `{:ok, _}`/`{:error, _}` signatures versus just raising exceptions. I ended up doing the latter for the additional benefit of stack traces. Exceptions seem culturally discouraged in Elixir, though I don't quite grok why yet. Out of curiosity, would you be willing to show how you might refactor the post's "create_organization" example? [0] https://github.com/rmies/monad https://github.com/rmies/monad
- tuvistavie 11y agoExceptions are not discouraged, using them for flow control is. There are I think a few reasons why exceptions are a being a little frowned upon. The first one is that usually we do not want to rescue them, the let it crash philosophy. If an error happens, the the process will crash, it will be restarted, the error will be logged, and everyone will be happy. In the case we really do want to handle an error, we can usually just use `case` or `with`, which are simpler, avoids to rescuing something we did not expect, as well as some pitfalls, for example the fact that recursive calls inside a `try` cannot be optimized to be tail-recursive, which can be an issue depending on the case. However, I think it is fine to use exceptions if it can avoid deeply nested code, which was probably your case.