giddie
GitHub: GitHub - flexibility-org/ok_then: The Swiss Army Knife for tagged tuple pipelines · GitHub
HexDocs: OK then... — ok_then v1.1.0
I just couldn’t quite find what I was looking for when it came to handling tagged tuples. In particular, I really wanted to find an elegant solution to the problem of missing return values that are not errors. Because sometimes, a missing return value is an error, and other times it’s not. Rust handles this very elegantly, using separate Result (Ok / Error) and Option (Some / None) types, often in tandem.
So I decided I had to write my own solution for handling tagged tuples. And I’m quite pleased with the result. Of course I have to give credit to those who have gone before, such as the authors of the “OK” and “Croma” packages.
The most controversial feature will be the addition of :none alongside :ok and :error, though this becomes mostly transparent in pipelines.
There are a few more functions I’d like to add, in particular to the Result.Enum module. And I’d also like a monadic “with”-like macro, similar to that offered by the “OK” package.
Trending in Announcing
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
- #ai
- #ecto-query
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #metaprogramming
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
wmnnd
Seems like a neat idea! You’ve already mentioned that you think adding
:nonemight be controversial - why do you think it’s still useful?After all,
nilis also simply an atom (is_atom(nil) == trueandnil == :nil)dorgan
Erlang uses
:nonein it’s standard lib, so not it’s not unheard of, but sincenilis already the convention in elixir, I have the same question.The exceptional library does that, a comparison between your solution and that one would be nice
ondrej-tucek
You haven’t found Result library? And course for
there is ExMaybe..
hauleth
I always have found
undefinedas a default value meaning “not found”.dorgan
There are some functions that return none, like
erlang:dist_ctrl_get_data,erlang:system_info, but it seems in those cases it means “set to none” or “not available” rather than “not found”giddie
There are two main reasons:
Semantics: There is no particular convention to determine the meaning of
nil- when I seenilin an error message, the cause (generally a missing nil-check) could be anywhere in that stack trace. However, by wrappingnilinto:none, I know that when I see:nonein a stack trace, I’m forgetting to pattern-match a tagged tuple, usually right at the top of the stack trace. Technically, the difference is very small - as you say, bothniland:noneare atoms - but in practice the name difference is helpful.Another way to look at this is by typing:
:noneis a valid value for the typeResult.maybe(any()), whereasnilis the absence of any value at all. It could have been:no_resultor something similarly more explicit to mark the “type” of the tuple, but I decided it was more pragmatic to keep the tag name short, and “none” is also easier to mirror in function names than the latter.Consistency: To make functions more generic, results can be normalized into two-element tuples:
{atom(), any()}. This happens internally, but you can also see this in functions such asor_else. For atom results such as:ok, this becomes{:ok, {}}. For:nonethis becomes{:none, {}}. Althoughnilis an atom, I think it’s a little less clear:{nil, {}}. It just looks a bit too much like a mistake or oversight to me - as if the tag itself is missing or undefined, which is not the intended meaning.giddie
I don’t think it addresses optional values (e.g. accidentally mapping a wrapped value to
nil)? I may have missed it. I did examine that package briefly, but the functionality seems to be aimed mainly at handling exceptions in pipelines, which is also a really nice goal, but different. I felt I first wanted something focused on a really clean and consistent API for regular tagged tuples. So far I’ve managed to mostly avoid exceptions in pipelines by catching them early, near the cause, but I’d definitely tryexceptionalif that became impractical.dimitarvp
What does a new convention of
:ok/:noneachieve that hasn’t been achieved by the:ok/:errorconvention?If a function does not return data then it can just return
:ok(not a tuple).When you say semantics and consistency, I am not seeing it. It’s OK to have a personal preference but I don’t see the value.
As for
nil, well, I think we all know that’s a huge and very expensive mistake in computer science in general. But as others pointed out,nilis also an atom so replacing it with another one doesn’t help much. Not sure I am seeing the argument for the stack traces as well.Maybe I just don’t get it though.
giddie
Yes; I did check out that library. Again, it doesn’t really handle optional values, and results are expected to only be tagged
:okor:error, whereasok_thenaccepts any tagged tuple (though:ok,:error, and:nonehave more convenient functions). I also felt like the API wasn’t quite as full or consistent as I’d have liked.As far as I can tell, this package adopts the
value | nilparadigm, though it does offer some semantics that make it easier to chain multiple nil-checks together in a pipeline. It doesn’t scratch my itch, because I feel that tagged tuples are semantically clearer and also safer.giddie
You mean it could return
:ok | {:ok, value}? Yes, you could certainly do that. Personally I find it less clear, because it’s not immediately clear that:okis a result that could have held a value. It could be made explicit with{:ok, {}}, for instance, and in factok_theninterprets:okin that way:So imagine that instead of tagged tuples, we use structs:
In every case, what is returned is a
Result, which should be unwrapped before it’s used directly. The spec for a function returning this would useResult.t()(or similar). In every case, it’s a Result that is returned. Returningnilwould clearly mean that you’re not returning a Result.What I’m suggesting is that semantically,
:ok | :error | :noneis exactly the same. The type isResult.maybe(). Returningnilwould mean that we’re not returning aResult.maybe(). This helps because if I see an error such as:none.hello/0 is undefined, then I know what I’m dealing with is aResult.maybe()pretty much exactly where the exception was raised that has not been unwrapped - it’s being treated as an unwrapped value. Unwrapping the result withunwrap_or/2will force you to consider a default value. If I see an error such asnil.hello/0 is undefined, or - more likely - an obscure Ecto exception caused by a nil leaking into a query - I need to work up the stack to find out where I should have checked for thatnil, because there’s nothing to “unwrap” here - it’s simply a value that isn’t there.