Noether aims to ease common data manipulation tasks by introducing simple algebraic functions and other utilities. Functions and names are inspired (sometimes taken as-is) from Haskell.
The Maybe module introduces operations on nullable values.
The Either module introduces operations on {:ok, _} | {:error, _} values.
The List module introduces operations on lists.
The root module has a few simple functions one might find of use.
def deps do
[
{:noether, "~> 1.0.0"}
]
endHere is a list of real world scenarios where you may find that using constructs like Maybe and Either make your code less verbose, more straightforward, and easier to read.
Suppose you have a function that returns a list of items, and you want to take the first element (if the list is not empty), apply a function to it, and wrap it in a nice {:ok, _} or {:error, _} tuple.
Without Noether, you would write something like this:
function_that_returns_list_of_items()
|> List.first()
|> update_item(&f/1)
|> case do
nil ->
{:error, :not_found}
item ->
{:ok, item}
end
defp update_item(nil), do: nil
defp update_item(item, f), do: f.(item)That's kind of verbose, especially since you need to type the functions that pattern match on nil and those that wrap a result in a tuple. Moreover, what if function_that_returns_list_of_items does not return just a list, but it may return an error as well? That's another case do!
Let's see how we could accomplish the same with Noether:
alias Noether.Maybe
function_that_returns_list_of_items()
|> List.first()
|> Maybe.map(&f/1)
|> Maybe.required(:not_found)Maybe operates on nullable values, while Either operates on {:ok, _} or {:error, _} tuples. Let's see how we can reduce the verbosity of elixir with operator using Either.bind/2.
Suppose you have N chained calls to different functions, where each one may return a tuple, and finally you want to return the "unwrapped" result to the caller. Normally, you would accomplish it this way:
with {:ok, _res1} <- f1(),
{:ok, _res2} <- f2(),
{:ok, _res3} <- f3(),
{:ok, res4} <- f4() do
res4
endIt can easily get frustrating and error-prone to write everytime the same {:ok, _} matches. Let's see how we can do this using Noether:
alias Noether.Either
alias Noether.List
[f1(), f2(), f3(), f4()]
|> List.sequence()
|> Either.unwrap()Easier to read, less verbose, and it encapsulates the handling of {:ok, _} tuples. You can focus on writing actual logic instead of repeating the same pattern matches every time.
Feel free to propose any function you deem useful and even vaguely related to the ones currently present.
Regarding naming, we have a couple of conventions:
- function names should be taken from Haskell if they exist, aliases with Scala naming are possible (e.g.
bindis aliased intoflat_map) - function arguments are named
a, b, c ...if values (exception:default),f, g, h ...if functions
mix test runs the tests.
mix format.all formats all the files under lib/.
mix check checks if the files are formatted; it then runs a linter (credo) and a type checker (dyalixir).