jswny
Hi all! I’m fairly new to Elixir. I’ve read a book or two, but am just getting into using the language in practice. I recently needed to write a simple function to update a key in a JSON file. However, I found myself running into a lot of repetitive Go-esque error checking. I’m new to functional programming and I was wondering if there is a more idomatic way to handle this type of situation, or just a way to improve it. I’m not a fan of this style as it is because it’s a huge block of ever-indenting lines like old JavaScript. Here is a slimmed down example I came up with:
defmodule Test do
@doc """
Inserts the given JSON into the `data` field of `file`.
"""
def insert(file, data) do
# Read the contents of the file
case File.read(file) do
{:ok, content} ->
# Decode the file to JSON
case Poison.decode(content) do
{:ok, decoded_content} ->
# Encode the given data to JSON
case Poison.encode(data) do
{:ok, encoded_data} ->
# Prepare the updated data for insertion
final_data = Map.update(decoded_content, "data", encoded_data, fn _ -> encoded_data end)
# Encode the updated data
case Poison.encode(final_data) do
{:ok, encoded_final_data} ->
# Write the updated data to the file
case File.write(file, encoded_final_data) do
:ok -> IO.puts "Successfully updated file!"
{:error, reason} -> IO.puts "Could not write to file because #{reason}!"
end
{:error, {:invalid, reason}} -> IO.puts "Could not encode updated JSON because #{reason}!"
end
{:error, {:invalid, reason}} -> IO.puts "Could not encode given JSON because #{reason}!"
end
{:error, :invalid} -> IO.puts "Could not parse file to JSON!"
{:error, :invalid, reason} -> IO.puts "Could not parse file to JSON because #{reason}!"
end
{:error, reason} -> IO.puts "Could not read file because #{reason}!"
end
end
end
Keep in mind, I just wrote this as a quick example. It’s not my actual code nor is it perfect code. I’m just trying to demonstrate the error handling pattern I’m Talking about. Thanks in advance!
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
sergio
I got this tip courtesy of the Pragmatic Studio Elixir course.
Instead of nesting case statements, use function pattern matching to handle conditional branches.
And so on.
svarlet
I’m currently playing with the Exceptional library:
In short, it explains that error tuples are annoying for error handling. They break pipelines, they have variable shapes which makes them hard to pattern-match ({:error, :reason} or sometimes {:error, {:foo, :bar}} or whatever). On the other hand, pattern matching on maps gives you total flexibility in terms of what your error value can contain. So instead of returning error tuples, what if you returned error maps? and Exceptions in elixir are just structs which are maps.
Add a new operator
f ~> gwhich behaves likef |> gexcept that if the input is an exception then it will bypassgand return the exception. Yes we are not raising exceptions, but returning exceptions like any other values.Check the post on medium, it’s well explained and that is working well for me now.
dom
You can use the with/else syntax in some cases, pipelining through several functions that match on their input and return it unmodified if it’s an error is also an option.
More generally though, it’s better not to handle an error case explicitly unless it’s expected to happen and you need to send a helpful message back to the caller/user. A supervisor crash log is a lot more useful for debugging than a vague log message like “could not read file” etc. See Erlang and code style: Musings on mostly defensive programming styles.
yurko
Using with helps with that kind of logic, basically you describe the happy path and can be as generic or specific as you want when handling errors (s.
elseexample).peerreynders
All the other advice is good - but at the most basic level keep your functions small, tiny even - e.g.:
This is essentially just a starting point for all the other pieces of advice you are getting. Once things are broken down in this manner it becomes much easier to factor things out.
Never forget that everything is an expression (forget about statements).
Crowdhailer
I wrote about how i think about this problem a while ago. Handling Errors in Elixir, No one say Monad.
In summary I think
withis useful but I prefer to sacrifice the flexibility of with for something that only handles{:ok, value}and{:error, reason}and built a library just for those cases. GitHub - CrowdHailer/OK: Elegant error/exception handling in Elixir, with result monads. · GitHubFinally keeping function small is always good ideas. If you have 8 places that can throw an error ( no matter how neat the code) its probably too many
aseigo
.. but they don’t have to. See GitHub - CrowdHailer/OK: Elegant error/exception handling in Elixir, with result monads. · GitHub for a way this could be done.
edit: aaaand now I see that @Crowdhailer posted that same link, too … if only I’d read the thread all the way. oops!
jswny
Thank you all for your informative responses! Because this functionality is only a small section of my app, I don’t want to use any libraries so I’ve taken the advice and refactored my code using functional pattern matching like
handle_file_read({:ok, content}), etc. However, I must say the libraries provided in this thread do look enticing and I would certainly consider them if my app had a larger need for such error handling. I’m going to try to use with statements as well to improve my code as I haven’t used those before or in other languages that I’ve written in even. Thanks again everyone!jswny
I’d like to respond to @dom’s suggestion separately. I’m very much considering dropping all of this error handling that I’m doing manually since all I am really accomplishing is printing out user-friendly error messages each step of the way so that the user does not have to decode them. Since I’m still raising exceptions and stopping execution each time I get something like an
{:error, reason}response, it would make sense for me to take the optimistic route and let the functions naturally raise exceptions themselves if something is wrong. However, the end-user would not easily be able to decode these messages. It seems like a trade-off of code maintainability and conciseness for user-friendliness that I have to weigh.peerreynders
In implementations with imperative languages I often encounter a certain unwillingness to acknowledge the complexity burden that detailed error handling imposes which typically devolves in rampaging Arrow(head)s (especially in JavaScript) and insufficient separation/segregation of “happy path” from “unhappy path” code. “Unhappy path” code deserves the same “management” consideration as the “happy path” code that the domain logic is primarily concerned with.
And ultimately “error management” ignores the fact that not all kinds of errors can be predicted.