Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing bare :ok and :error values #66

Open
nietaki opened this issue Nov 8, 2019 · 3 comments
Open

Allowing bare :ok and :error values #66

nietaki opened this issue Nov 8, 2019 · 3 comments

Comments

@nietaki
Copy link

nietaki commented Nov 8, 2019

Context

I recently realised more and more code I'm writing in my day job is half-hearted error handling for which OK-style ~result monad would be the better solution.

At the same time, the main reason I don't introduce it to the codebase is that many of the functions in the system that are used just for their side-effects have a return value of :ok | {:error, reason}. Or, similarly, we're using standard library functions like Map.fetch(map(), key()) :: {:ok, value()} | :error.

As much as I personally like OK, I can't really make an argument that all functions that don't return correctly formed 2-tuple results should be re-written or wrapped, just so that we can start using the library.

I believe the lack of ability to handle bare :ok and :error values significantly impedes the widespread adoption of OK.

Semantics

I believe wherever an {:ok, term} | {:error, term} is expected, an :ok or :error could be interpreted as {:ok | :error, nil}. I can't think of a case where it would be really problematic.

I imagine however you might not like the semantics change, I remember you being pretty committed to keeping it strict and there's a note about it in the readme. One could potentially make an argument that there might exist users for whom it is important that OK enforces the 2-tuple shape of the function returns.

Depending on the implementation we can keep or not keep the backwards compatibility

Implementation options

Note, I haven't carefully read and thought about the current implementation, some of these might be harder to execute on than I imagine.

1. Just change the semantics, keep the interface as it is.

I think I might be up for that, but I wouldn't be surprised @CrowdHailer and others weren't happy with that.

Would probably mean releasing OK 3.0

2. Create lenient versions of all macros / functions in OK module

I'm not sure what the right names for the new functions would be. Also, transitioning from strict to lenient OK in any project would be an ugly change.

3. Create a lenient version of the OK module

The module would basically rewrite whatever OK does with the new semantics, with as much code re-use as possible. The implementation would probably end up a bit ugly, but using it and transitioning from OK to "lenient OK" would probably be painless.

I think something like this would work:

require OK.LenientOK, as: OK

# the rest of the code unchanged

4. Some clever option with use

I'm not sure how that would look like in its best version. Also, from my limited understanding of Elixir macros, I feel like having use in many modules could have a bad impact on compilation time, as it would be independently executed for each of the modules. It's just my assumption though, probably worth confirming.

Also, personally I like to not use use if I can help it, it introduces unnecessary magic that might be hard to debug.

5. Something else

There's probably other options, I can't think of any good ones right now, but I'm open to suggestions.

Closing notes

I think I like (1), if that's not an option, I think (3) could give a good end result.

As usual, I'd be down for doing the implementation if we decide on going ahead with any of the options and it looks viable.

@CrowdHailer
Copy link
Owner

Thoughts:

  1. Fair enough, I'm not sure if it requires a 3.0 as it is just getting more permissive, but might break dialyzer specs so probably 3.0
  2. Not so good and you'd need versions of ~>> which can have a different name because there is a limited set of symbols that work
  3. Fine, and allows it to exist before 3.0. getting the reuse between modules might be tricky.
  4. I don't think this is a good call,
  5. can't think of any others.

I have mellowed in my opinion on keeping everything strict. Caring about the details of typeclasses/monad is not a trait of the Elixir community, so working with what's around does make sense.
I think that really this library should have one set of semantics, i.e. there shouldn't be both lenient and strict versions of the same thing in the library. *(with caveat)

I would accept option 3 now, module name can be anything. This could be released as 2.x and allow people to try it out. I don't think there is any fundamental problem with the proposal of working with bare atoms But I would like to be sure, buy having some test cases in the wild.

Eventually if option 3 was a success I would probably suggest moving it over to the main API as a 3.0 release

@c-manakov
Copy link

Are there any updates on this? If there's no one to work on the implementation, I can help contribute to it, a lot of functions in standard Elixir return bare :ok values.

@kim366
Copy link

kim366 commented Jun 20, 2021

It may also make sense to allow more than 2 length tuples. I have found some functions doing this and I like returning longer tuples myself. Here's an example:

iex(6)> :uri_string.parse("##")  
{:error, :invalid_uri, '#'}

(although this may be a bad example, since it doesn't return an ok-tuple on success)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants