giddie

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.

Showing Posts 1 to 10

wmnnd

wmnnd

Seems like a neat idea! You’ve already mentioned that you think adding :none might be controversial - why do you think it’s still useful?
After all, nil is also simply an atom (is_atom(nil) == true and nil == :nil) :slight_smile:

dorgan

dorgan

Erlang uses :none in it’s standard lib, so not it’s not unheard of, but since nil is 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 :slight_smile:

ondrej-tucek

ondrej-tucek

You haven’t found Result library? And course for

there is no standardised pattern to represent optional values , other than value | nil

there is ExMaybe..

hauleth

hauleth

I always have found undefined as a default value meaning “not found”.

dorgan

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

giddie OP

There are two main reasons:

Semantics: There is no particular convention to determine the meaning of nil - when I see nil in an error message, the cause (generally a missing nil-check) could be anywhere in that stack trace. However, by wrapping nil into :none, I know that when I see :none in 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, both nil and :none are atoms - but in practice the name difference is helpful.

Another way to look at this is by typing: :none is a valid value for the type Result.maybe(any()), whereas nil is the absence of any value at all. It could have been :no_result or 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 as or_else. For atom results such as :ok, this becomes {:ok, {}}. For :none this becomes {:none, {}}. Although nil is 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

giddie OP

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 try exceptional if that became impractical.

dimitarvp

dimitarvp

What does a new convention of :ok / :none achieve that hasn’t been achieved by the :ok / :error convention?

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, nil is 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

giddie OP

Yes; I did check out that library. Again, it doesn’t really handle optional values, and results are expected to only be tagged :ok or :error, whereas ok_then accepts any tagged tuple (though :ok, :error, and :none have 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 | nil paradigm, 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

giddie OP

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 :ok is a result that could have held a value. It could be made explicit with {:ok, {}}, for instance, and in fact ok_then interprets :ok in that way:

def example(value_r) do
  value_r
  |> Result.map(fn
    {} -> "default"
    value -> value
  end)
  |> Result.unwrap!()
end

{:ok, "hello"} |> example()   # "hello"
:ok |> example()              # "default"
:error |> example()           # **(ArgumentError)

So imagine that instead of tagged tuples, we use structs:

%Result{is_empty: false, is_ok: true, value: "hello"}
%Result{is_empty: true, is_ok: true, value: nil}
%Result{is_empty: false, is_ok: false, value: "reason"}

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 use Result.t() (or similar). In every case, it’s a Result that is returned. Returning nil would clearly mean that you’re not returning a Result.

What I’m suggesting is that semantically, :ok | :error | :none is exactly the same. The type is Result.maybe(). Returning nil would mean that we’re not returning a Result.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 a Result.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 with unwrap_or/2 will force you to consider a default value. If I see an error such as nil.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 that nil, because there’s nothing to “unwrap” here - it’s simply a value that isn’t there.

Where Next? Top

Trending in Announcing Top

mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
danschultzer
I’ve moved the strategies out of PowAssent and created a multi-provider framework for Elixir. This way the strategies can be used in any ...
New
gtcode
Aloha gang, There’s a new class of AI model out from TypeSafe. Jev is their flagship model. You can learn about it here: ...
New
quatermain
Hello, I’m sharing my plugin here in forum after some time so it has time to mature and proof yourself. I use Claude Code daily on a pr...
New
danschultzer
I needed something like premailer for rails, and after searching, I decided to make an easy-to-use library to handle inline styles and co...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
AstonJ
With advancements in AI gathering pace and set to increasingly accelerate, perhaps it’s worth having an annual thread on the topic - feel...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews