I’m heading off for the weekend after work and since I ran my mouth pretty hard about asking questions in that other thread, I wanted to respond! There is a lot of content here, maybe a bit much for one thread (maybe not). I’d love to talk about most of it but since I don’t have much time I’m gonna zero in on the return values because that one interests me and is also related to “let it crash”.
In short, {:ok, resp}
and {:error, message}
specially is simply a convention used when something can go wrong. @stefanchrobot had a perfect example in this answer. As illustrated there, it’s often used where an exception would be thrown in other languages. I don’t have a lot of experience in languages where frequent exception-throwing is the norm but as I see it, this enforces explicit error handling at the source and frees up exceptions for cases that are truly “exceptional”. Without getting too much into it, this is where “let it crash” ties in. If you know how to handle something, by all means handle it! But ideally do it through some kind of well-formed return value and leave exceptions to be caught by supervisors. I could get more into this but trying to stay focus 
So getting back on track, you essentially want to use the tuple convention when you need some kind of status code, and it doesn’t have to be :ok
/:error
, again that is just a convention. You could have a function that makes an HTTP request and could have return values like {200, "body"}
, {400, "body"}
, {500, "body"}
etc. If your functional doesn’t need to check a status, for example String.capitalize/1
, just return a bare value. It would be pointless, not to mention super annoying, if String.capitalize("hello")
returned {:ok, "Hello"}
since there is nothing else other than :ok
to match on. A string is always going to successfully capitalize and if doesn’t, there is something seriously wrong and let it crash
(That is a super contrived “let it crash” example but I’m kind of rushing here).
Lastly, a simple :ok
it returned when there is no other meaningful data to return in the success case. If the error case doesn’t have a message to go with it (which would be weird) you could just return :error
, but generally it has a message so they are wrapped in a tuple. You could also just return a bare string in the error case if you really wanted—again, these are all just conventions. IE, there is no need the different return possibilities to be wrapped in the same data structure. For example, ExUnit.Callbacks.setup/1
can return :ok, {:ok, %{}}
, or simply %{}
. It pretty much comes down to {:ok}
is just weird because a one-element tuple doesn’t make any sense. And in fact, it’s not as inconsistent as you might think since in Haskell (and possibly other functional languages), tuples of different lengths aren’t considered to be of the same type!
Anyway, I hope this helps a bit. I apologize that it’s a bit verbose—I would normally try and edit it down, but I’m now late for work and still have to pack for the weekend!
Edited to fix a small but significant typo: (I wrote “consistent” instead of “inconsistent”!)