OndrejValenta
Based on our discussion here and to create some kind of guide for new players..
What are your guidelines and recommendations on method signatures and return values?
Since everyone can get creative in a dynamic language like Elixir you probably have some guidelines in your companies on how to define new methods and what should they return so you have easier code transfers from one programmer to another.
For example:
-
How many parameters is too much for a function? When do you rather create a structure to contain the incoming data?
-
Do you rather use single parameters or do you prefer receiving a map that you map and deconstruct?
-
What is your ordering of parameters? Do you put the most static parameters to front or back?
-
Do you override methods with specific mappings or rather have one method with a case inside?
-
What do you return from methods? Are there methods that are returning just plain values in your projects and when do you switch to {:ok, data…} tuples?
-
Do you return {:ok} or just plain :ok? For me {:ok} is more consistent with {:ok, data}, for others it’s not.
-
How many return values do you put in your return statements? Just one or two? For example, {:ok, data}, {:ok, data1, data2}
-
When do you create a return structure?
More questions will come from the discussion.
Trending in Discussions
Other Trending Topics
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex











Showing Posts 1 to 10- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
sodapopcan
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 focusSo 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
(That is a super contrived “let it crash” example but I’m kind of rushing here).
: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 exampleString.capitalize/1, just return a bare value. It would be pointless, not to mention super annoying, ifString.capitalize("hello")returned{:ok, "Hello"}since there is nothing else other than:okto match on. A string is always going to successfully capitalize and if doesn’t, there is something seriously wrong and let it crashLastly, a simple
:okit 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/1can 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”!)
OndrejValenta
Ok, to elaborate more on this.. if you have a multiple errors that a method can return, say file doesn’t exist, file is currently locked by another process, file is too large to process.
Would you return {:error, {:file_too_large, “file path”}} or just {:err_file_to_large, “file path”}, I would choose the former, just asking what do you prefer.
sodapopcan
Good question! I’ve never run into that. I think that comes down to taste. In these cases I like to do some “wishful programming” and see what the implementation looks like
vs
I personally prefer the second as it’s just more concise. Since I would hope that anything other than
:okwould be an error, I don’t feel adding the extra:errortag adds much value. But I really feel this is a of taste. If you do like the:errortag,{:error, :file_too_large, path}is also perfectly legit. You did ask about tuple size. For me I generally think 2-3 is good. 4 is also good but starting to push it. I pretty much avoid 5 completely and would use a map at that point. But I really stress that this is a matter of taste and what you find readable.LostKobrakai
I really like the approach described in this, though I need to add that I never managed to work on a codebase, which consistantly did that. It’s for sure overhead, but on the other hand I really like the explicitness.
al2o3cr
IMO this choice is very context-dependent and driven by usage:
{:error, any()}result than{atom(), any()}OndrejValenta
Actually, this makes a lot of sense, I like it.. With this approach there is clear understanding that when an error the method returns an error it is announced with :error atom and you don’t have to think about if the first value is just an value or it’s an error.
So following code is what I will use.. It’s more code but it speaks more loudly..
OndrejValenta
Just a simple map and not any kind of defstruct, for example OrderProcessingResult? I’m not sure if people use defstructs or it’s just too much hassle.
kasvith
hmm this looks fine until you forgot to implement
format_erroron a moduleLostKobrakai
Oh god. I did just skim over the article and it mentioned the places I had read before. I’m mostly in favor of the
{:error, exception}return value instead of{:error, something}. Exceptions have API to be turned into strings, they however can include structured data, which might be interesting if the caller wants to log the error. I don’t think the exact implementation shown makes too much sense.gregvaughn
I first saw that done in the exceptional library. I advocated for it in my talk at last year’s ElixirConf US, but I’ve only used it for one particular situation.
Note: I haven’t actually used the library at all, just the approach of returning
{:error, exception}