{:ok, ...} return tuples ?

1,367 views
Skip to first unread message

mraptor

unread,
Sep 20, 2015, 4:04:15 PM9/20/15
to elixir-lang-talk
Hi,

I can see that using {:ok, .. } and similar tuples are used all over the place and it is convention borrowed from Erlang.
I just can't see the purpose of it or to be more correct the advantage of using it, compared to just returning the value.
I would even opine that it makes handling returns more complicated in many cases.
Normally in other languages you will see for error functions returning : nil, -1, 0 or some such depending on the situation.
Can you give me an example that clearly wont work with normal return values ?
The only time I've needed something like this in other languages is in the case of "ZERO but True" return value.

Probably the obvious answer is staring me in the face :), and I will feel embarrassed  when you tell me, but hey !! please let me know.

Lance Halvorsen

unread,
Sep 20, 2015, 4:17:52 PM9/20/15
to elixir-l...@googlegroups.com
Hi,

As luck would have it, my talk for ElixirConf covers this issue. :^) 

Without spoiling too much of the fun of the talk, the tuple return values can be used in conjunction with pattern matching and multi-clause functions to eliminate conditionals. Consider this:

def insert_model(model) do
  MyApp.Repo.insert(model) |> handle_insert
end

defp handle_insert({:ok, model}) do
  # do insert things
end
defp handle_insert({:error, changeset}) do
  # do error things
end

With this, we cleanly separate the behaviors for success and failure into distinct clauses, with no branching conditional. If the behaviors inside of each clause are small, maybe this isn't a big deal. If they are large or complex, though, this can really increase readability.

Hope that helps!
.L

--
You received this message because you are subscribed to the Google Groups "elixir-lang-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-ta...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-talk/3f497f44-f96b-4bcd-81a2-382bf4ed3024%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Theron Boerner

unread,
Sep 20, 2015, 4:20:53 PM9/20/15
to elixir-lang-talk
It's so that you can match against {:ok, _}. If the function had errored, it would have returned {:error, _} and by matching against it you can either handle failure or crash if there's a problem. Returning nil is more complicated because if I want to say: continue ONLY if the function returns a good value then I would have to have a case statement and only proceed if the value is NOT nil. Also, it's much easier to work with than throwing errors.

--

Peter Hamilton

unread,
Sep 20, 2015, 4:21:14 PM9/20/15
to elixir-lang-talk
Part of this is the "let it crash" philosophy. It's really common to see things like:

:ok = do_foo()

It's a really fast way to say "I only know how to proceed if do_foo() succeeds, so if anything else happens we should crash." It's akin to:

if (do_foo())
  exit(0)

Aside from "let it crash", Erlang and Elixir lack a powerful type system with ADTs and stuff. This historically has been overcome just by using tuples with the first element being an atom that encapsulates the semantic type. 

So in Haskell we might have something of type `Either Int Exception` with values `Left 1` or `Right IOError`, in Elixir we have `{:ok, 1}` and `{:ioerror, "Could not open file"}`.  Naively you might think that returning `1` and `{:ioerror, "Could not open file"}` would be sufficient, but what happens when you want to return a 2-tuple? Pattern matching becomes more difficult.

This notion of a type system may be obsolete with Structs, which are essentially the same concept but have a field in the map containing the type rather than the first element of the tuple. However, maps are still young and aren't quite as fast as tuples.

--

Uniaika

unread,
Sep 20, 2015, 4:27:25 PM9/20/15
to elixir-l...@googlegroups.com
We basically use the {:atom, foobar} tuples instead of exceptions, and
because you can more easily define an internal API in your application.
Returning {:ok, _}, {:error, _} or even {:flag, _} is a bit less obscure
than returning a simple digit :)

I use it for instance in
https://github.com/Annwenn/Exon/blob/master/lib/exon/Database.ex#L63-L68 :

defp get_id_informations(id, table_name) when is_integer(id) do
case :ets.lookup(table_name, id) do
[{id,informations }] -> {:ok, {id, informations}}
_ -> {:error, :id_not_found, id}
end
end

Jim Menard

unread,
Sep 20, 2015, 6:48:28 PM9/20/15
to elixir-l...@googlegroups.com
Exactly. In short, {:ok, nil} might be a perfectly good normal
response. In the database world "nil" or "null" can mean "I don't know
what this value is. It may have a value, but I don't know it."
> https://groups.google.com/d/msgid/elixir-lang-talk/CAOtk34cS3zjTTKMQOSkjQgg-Lf84BCfX1nWL48ATxsCSt-89wA%40mail.gmail.com.
>
> For more options, visit https://groups.google.com/d/optout.



--
Jim Menard, http://www.jimmenard.com/

Kevin Montuori

unread,
Sep 21, 2015, 1:15:22 PM9/21/15
to mraptor, elixir-lang-talk
>>>>> "m" == mraptor <mra...@gmail.com> writes:

m> Can you give me an example that clearly wont work with normal
m> return values?

See Dict.fetch/2. Also, {:ok, value} *is* a normal return value.

k.


--
Kevin Montuori
mont...@gmail.com

Joel Meyer

unread,
Sep 24, 2015, 6:52:17 PM9/24/15
to elixir-lang-talk, mra...@gmail.com
Along these lines, I had code in a few places that looked like:

def fun1 do
  {:ok, val} = do_some_thing
end

def fun2 do
  {:ok, val} = do_something_else
  operate_on(val)
end

These are obviously contrived examples, but it occurred to me that I could just create a function:

defp ok({:ok, val}), do val

and then use the pipe operator instead:

def fun1 do
  do_some_thing |> ok
end

def fun2 do
  do_something_else |> ok |> operate_on
end

Before I do that, though, I wondered if it was in some way anti-idiomatic elixir or introducing a code smell. Is there a better way of handling that?

Thanks,
Joel

Kevin Montuori

unread,
Sep 24, 2015, 7:28:53 PM9/24/15
to elixir-lang-talk
>>>>> "jm" == Joel Meyer <joel....@gmail.com> writes:

jm> Before I do that, though, I wondered if it was in some way
jm> anti-idiomatic elixir or introducing a code smell. Is there a
jm> better way of handling that?

You might want to take a look at one of the monad libraries available on
hex. For instance Towel and Monk both have functions that you might
find useful (I believe there are others as well).

In my own code I generally provide function/x that returns a tagged
value and function!/x that returns a plain value or raises an exception.
You'll note that much of the stdlib does that as well.

Richard B

unread,
Sep 24, 2015, 8:52:03 PM9/24/15
to elixir-lang-talk
I always have mixed feelings about the bang functions. I don't think I ever use them in production code. I mostly treat them as a convenience for when I'm spiking things out in IEx. Given the philosophy of Erlang with regard to "Let it crash" and pattern matching I think we should be paying attention to success modes. Writing reliable software is about not falling asleep at the wheel and I find matching on the {:ok/:error, val} tuples helps me think about that.

As an example, I tried to give an example of how you should use a function like File.read/File.read! failing but given all sorts of surrounding context it might be appropriate to let it crash or pattern match. It also depends how many ways can the function fail? File.read! won't fail too many different ways so a single rescue statement can handle that. But if File.read could fail 3 or 4 or more ways it's probably nicer to explicitly handle those in separate pattern matches.

Also, when I see a {ok: val} = ... it is very obvious to me that this line of code is doing something interesting. Whereas with bang functions you can easily overlook them.

Booker Bense

unread,
Sep 24, 2015, 11:25:00 PM9/24/15
to elixir-lang-talk

FWIW, the majority of security bugs in other languages are directly related to functions that return special values
to indicate errors and the lack of proper handling of those errors. Take any major C language project, grep it for 
system calls and see if the code handles error conditions, if not you likely have a security bug...  

My experience with elixir is that things only look ugly and awkward when you bring intuitions and constructs 
from other languages and attempt to hammer the square peg into the round hole. 

If you feel like you're fighting the language to implement something, for me so far, that's been a giant red flag that 
there is a better way. Step back and re think what you're doing. 

- Booker C. Bense 
Message has been deleted

Kevin Montuori

unread,
Sep 25, 2015, 12:10:45 AM9/25/15
to Richard B, elixir-lang-talk
>>>>> "rb" == Richard B <rbis...@gmail.com> writes:

rb> I always have mixed feelings about the bang functions.

I'm okay with raising an exception; I don't think I've ever caught one
though. In a case where I'd like to run values through a pipeline I'll
use them and not catch the exception, that still feels pretty "let it
crash" to me and handy for a short lived piece of code. Your comments
about many ways to fail is spot on.

As you point out, explicitly capturing the tagged return is good
documentation and a better practice. I differentiate some between
run-and-done code and long lived supervised processes, limiting the fast
and loose to the former.

As an aside: nice discussion. I've been typing more Erlang than Elixir
for the past few months and find that some of what Erlang takes heat for
(examples recently mentioned here: tagged return values and the
verbosity of always including the module name) are things I don't want
to (and don't have to, fortunately) give up with Elixir. I hope the
Elixir culture retains the maybe antiquated seeming but damn effective
-- I was about to say explicitness but I think I mean -- purposefullness
of well written Erlang programs.
Message has been deleted

Troels Brødsgaard

unread,
Sep 25, 2015, 8:45:31 AM9/25/15
to elixir-lang-talk
The need for bang functions confused me as well, until I heard an explanation by José on Ruby Rouges[1]. Basically, he said to use bang functions only when there is no way to proceed or recover from the error. For example, if your program depends on being able to read a config file, and cannot continue without, you should use File.read! The advantage they provide is the nicely formatted error messages, instead of BadMatch, which can be very confusing to newcomers.

However, bang functions should not be used for control flow - always use tagged tuples in that case.

Best regards,
Reply all
Reply to author
Forward
0 new messages