Qqwy
Solution: library to do pattern-matching with general ok/error types in case/with statements -- v1.0.1
Solution is a library to help you with working with ok/error-tuples in case and with-expressions by exposing special matching macros, as well as some extra helper functions.
Rationale
ok/error tuples, which are also known by many other names some common ones being ‘Tagged Status’ tuples, ‘OK tuples’, ‘Success Tuples’, ‘Result tuples’, ‘Elixir Maybes’.
Working with these types is however a bit complicated, since functions of different libraries (including different approaches in the Elixir standard library and the Erlang standard library) indicate a successful or failure result, in practice, in one of the following formats:
{:ok, val}when everything went well{:error reason}when there was a failure.:ok, when everything went well but there is no useful return value to share.:error, when there was a failure bht there is no useful return value to share.{:ok, val, extra}ends up being used by some libraries that want to return two things on success.{:error, val, extra}ends up being used by some libraries that want to return two things on failure.- In general,
{:ok, ...}or{:error, ...}with more elements have seen some (albeit luckily limited) use.
Clearly, a simple pattern match does not cover all of these cases. This is where Solution comes in:
- It defines clever guard macros that match either of these groups (
is_ok(x),is_error(x),is_okerror(x)) - It defines macros to be used inside special
caseandwithstatements that use these guards and are also able to bind variables:
For instance, you might use ok() to match any ok-type datatype, and error() to match any error-type datatype.
But they will also bind variables for you: So you can use ok(x) to bind x = 42 regardless of whether {:ok, 42}, {:ok, 42, "foo"} or {:ok, 42, 3,1,4,1,5,9,2,6,5} was passed.
Examples
Guards
Solution exposes three guard-safe functions: is_ok(x), is_error(x) and is_okerror(x)
ok(x)will match:ok,{:ok, _},{:ok, _, _},{:ok, _, _, __}and any longer tuple whose first element is:ok.error(x)will match:error,{:error, _},{:error, _, _},{:error, _, _, __}and any longer tuple whose first element is:error.okerror(x)matches both of these.
Solution also exposes versions of these that take a ‘minimum-length’ as second argument. A length of 0 works jus the same as above versions. Longer lengths only match tuples that have at least that many elements (as well as starting with the appropriate tag).
SCase
Solution.scaseworks like a normal case-statement,
but will expand ok(), error() and okerror()macros to the left side of ->.
scase {:ok, 10} do
ok() -> "Yay!"
_ -> "Failure"
end
#=> "Yay!"
You can also pass arguments to ok(), error() or okerror() which will then be bound and available
to be used inside the case expression:
scase {:ok, "foo", 42} do
ok(res, extra) ->
"result: \#{res}, extra: \#{extra}"
_ ->
"Failure"
end
#=> "result: foo, extra: 42"
Note that for ok() and error(), the first argument will match the first element after the :ok or :error tag.
On the other hand, for okerror(), the first argument will match the tag :ok or :error.
SWith
Solution.swith works like a normal with-statement,
but will expand ok(), error() and okerror() macros to the left side of <-.
x = {:ok, 10}
y = {:ok, 33, 44, %{a: "other stuff"}}
swith ok(res) <- x,
ok(res2) <- y do
"We have: \#{res} \#{res2}"
else
_ -> "Failure"
end
#=> "We have: 10 33"
For more examples and more info about the helper functions, check the GitHub page or the Documentation. ![]()
Please let me know what you think!
Most Liked
Qqwy
Solution has had quite some time during which it seems like the interface as it currently stands works well.
Therefore, a stable 1.0 version will probably be released soon
.
Qqwy
An excellent question. I most certainly did!
Solution is meant to be more general and more lightweight/idiomatic in its approach.
Let’s compare it with the six other commonly mentioned libraries that work with ok/error tuples:
Comparing it to the ok library:
- OK requires ok/error tuples to always exactly have two elements (the first being
:okor:error, the second being a value). This means that plain:okor:errorare not handled, nor are results like{:ok, value, meta}which are relatively common in production Elixir (c.f. Ecto Multi). - OK introduces a new ‘keyword’ called
OK.forwhich sort of takes the place of Elixir’swithbut not completely (I think?), which is also subject to above caveat. - OK has a wrapper called
OK.trywhich is sort of likeOK.forbut wraps it in an extra try/rescue block (I think?). - OK contains a couple of custom operators to allow ‘monadic piping’.
Solution’s swith and scase statements on the other hand work 1:1 like the built-in counterparts, except that you can add the ok(...) and error(...) macros at the LHS of the matches to match any ok/error tuple that has at least the required length. I believe that the library therefore has much less of a learning curve and much less mental overhead.
Comparing it to the result library:
- Result has a bunch of monad-inspired functions, but no overloaded
caseorwithstatements. - Just like OK, Result only accepts exactly the format
{:ok, value}/{:error, problem}.
Comparing it to the exceptional library:
- Exceptional has features to declaw exceptions and turn them into plain structs or possibly ok/error tuples, and reraise them later. Handling exceptions is not a design goal of
Solution, so it does not have this functionality. - Exceptional also has custom ‘monadic piping’ operators.
Handling different kinds of tagged tuples is not a design goal of Exceptional, so it does not have functionality to work with these datatypes (other than a basic ‘normalization’ conversion, which also does not handle ok/error tuples with more than one value inside).EDIT: It’s slightly more nuanced than that. see this follow-up.
Comparing it to the towel library:
- Towel also takes the approach of a ‘monadic pipeline’. Not with special syntax this time, but using a couple of functions (that are probably part of your global namespace since it advocates to
use Towel). - Towel does not have support for values of the type
:ok/:error nor for{:ok, multiple, things}`. - Towel’s last commit was more than two years ago.
Comparing it to the ok_jose library:
- OkJose overloads
|>to work differently for ok/error tuples. - OkJose allows you to create other overloaded versions of
|>. - OkJose does not have support for values of the type
:ok/:error nor for{:ok, multiple, things}`. - OkJose’s last commit was more than two years ago, and the README mentions that it was made before
withwas available in Elixir.
So the Tl;Dr is that Solution has a slightly different approach to these libraries:
- It strives to support all possible ok/error values that are used in practice: Not only the two-element tuple versions, but also the single atoms
:okand:error, as well as the versions with more than two elements like{:ok, data, metadata}. - It strives to be lightweight by using the existing
caseandwithsyntax, rather than adding variants of the pipe-operator and/or require an understanding of monads to be used successfully. - It is focused on working with ok/error tuples. It does not deal with exceptions or other kind of failure indications.
I hope that answers your question! ![]()
Qqwy
As a heads-up: the issue has been resolved! ![]()
Version 1.0.1 has been released.
Popular in Announcing
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance










