Ok Jose
A tiny library for piping values on a given pattern like the erlang idiom {:ok, _} and {:error, _} tuples.
You can also define your own pattern-matched pipes besides ok and error.
Besides ok_jose I've made a couple more of related macro libraries
that might be related (or useful depending on your needs and taste):
Installation
- Add ok_jose to your list of dependencies in
mix.exs:
def deps do
[{:ok_jose, "~> 2.1.0"}]
end
Motivation
A lot of erlang libraries follow the convention of returning {:ok, _} and {:error, _} tuples to denote
the success or failure of a computation. It's my opinion that more people should embrace this convention
on their code, and this library helps by making it easier to pipe values by unwrapping them from the
tagged tuples.
This library is my try at having a beautiful syntax for a happy pipe, that is, a pipe that expects {:ok, _}
tuples to be returned by each piped function.
If any piped function returns a non matched value, the remaining functions expecting an {:ok, _} value wont get executed.
So, for example, the following code
filename
|> File.read()
|> case do
{:ok, content} ->
content |> Poison.Parser.parse()
{:error, _} = error -> error
end
can be written as:
{:ok, filename}
|> File.read
|> Poison.Parser.parse
|> ok
The main advantage of the macros defined by OkJose is that you dont need to learn new syntax it's just plain old Elixir piping. It's also very easy to define your own, as they are just case clauses.
Usage
use OkJose
Provides you with the following macros:
ok/2
Pipes values into functions as long as they match {:ok, _}
{:ok, v} |> f |> g |> ok
ok!/2
Pipes values into functions but if at any point a value
does not match {:ok, _} raises a match error.
error/2 and error!/2
Work exactly as the previous examples, but for tuples tagged as :error.
use OkJose.Pipe
defpipe
This module lets you define your own pipes, actually the
previous ok pipe is defined like the following. Any
value not matching is just piped as-is down the pipe.
defpipe ok do
{:ok, value} -> value
end
However if the pipe name ends with an exclamation mark, and no clause matches, an error will be raise just as with any function not matching on its arguments.
defpipe ok! do
{:ok, value} -> value
end
With these you can define a pipe for working on common values like ecto models and changesets.
defpipe ok_or_valid do
{:ok, value} -> value
changeset = %{valid?: true} -> changeset
end
This way you can pipe ok values or valid changesets like:
{:ok, %User{}}
|> cast(params, @required)
|> put_change(:name, "John")
|> Repo.insert
|> update_jwk_token
|> ok_or_valid
You can also define pipes that for values based on a predicate instead of just a pattern match. For example, if you need to call some function to determine if the value is safe to be passed through the rest of your pipe.
The OkJose.pipe_when macro takes a block of patterns and
expects each to return a form like {true, value} or {false, value}
The first boolean value on the tuple tells if the payload
is to be passed down the pipe or if the pipe must suspend execution.
This way you can use negative matches, for example the following pipe will only let safe pets to be given to functions:
The predicate ?-ending form of defpipe is just sugar for pipe_when
@dangerous [Lion, Wolf, Cocodrile]
defpipe pet_safe? do
pet = %{__struct__: mod} when not mod in @dangerous ->
{true, pet}
anything ->
{false, pet}
end
And you can use them by just adding to the end of the pipe
{:ok, pet}
|> take_out
|> go_to(:park)
|> play_with(:kids)
|> pet_safe?
About ok
OkJose was born a while ago, back before we had Elixir 1.0, and before Elixir had its
own with form to deal with this kind of problems. There are a lot of other libraries
for dealing with tagged tuples and in general for monads in elixir. I'd recommend you
to checkout these in particular:
-
okA growing library, also has a list of alternatives at their Readme. -
happyWork on OkJose leadme to creating happy and laterhappy_withThey are just a bit less-pipeable than OkJose, and more on the spirit of Elixir'swith -
pitA weird one, for transforming and matching data at every pipe step. I dont know what I was thinking while doing it, but maybe it can be of use to someone.
Is it any good?
Take a look at the tests for more code examples.
マクロス Makurosu
[Elixir macros,] The things I do for beautiful code ― George Martin, Game of Thrones