# Bindable
[![Elixir CI](https://github.com/iamafanasyev/bindable/actions/workflows/elixir.yml/badge.svg)](https://github.com/iamafanasyev/bindable/actions/workflows/elixir.yml)
Elixir for-comprehension that goes beyond the `List`s.
```elixir
import Bindable.ForComprehension
import Bindable.Maybe
bindable for x <- just(1),
y <- just(2),
x + y > 4,
z <- just(3),
do: x + y + z
# nothing()
```
## For-comprehension
For-comprehension is a common syntax construct in functional programming languages,
that allows you to express complex operations on collections and monadic contexts in a concise and expressive way.
More formally, it is a way to construct monadic value by describing it as a sequence of effectful computations.
To do so, it provides following constructs to combine such computations into new monadic value:
* Generator (commonly known as iterator for lists): extracts value from the monadic context.
* Guard (or filter): filters generated values based on a predicate (see `Bindable.Empty`).
* Assign: aliases any expression inside current scope.
* Yield: defines resulting value to return inside the monadic context.
```elixir
import Bindable.ForComprehension
xs = [[10, 20], [30]]
bindable for x <- xs, # generator
length(x) > 1, # guard
y <- x, # next generator
z = y + 1, # assign
y + z > 21, # another guard
do: {y, z} # yield
# [{20, 21}]
```
## Usage
Elixir's kernel provides for-comprehension ***only*** for lists.
It works with any `Enumerable`, however it always (eagerly) yields `List`
(so you can't *describe* `Stream` using `Kernel.SpecialForms.for/1`).
`Bindable` already comes with for-comprehension batteries for:
* `List`,
* `Maybe`,
* `Stream`.
"Minimal complete definition" for you data type to be compliant with for-comprehension includes:
* `Bindable.FlatMap` implementation to chain sequential generators;
* `Bindable.Pure` implementation to yield resulting value (design decision, see `Bindable.ForComprehension`).
If you want to use guards/filters inside for-comprehension with your data type,
you should also provide an implementation for `Bindable.Empty`, so it is optional,
e.g. when your data type does not provide any meaningful semantics for empty/filtered value effect.
***The main goal*** of the library is to provide for-comprehension beyond lists with *the least* amount of overhead.
So it doesn't aim to provide a principled way to define type classes (required by for-context),
e.g. it doesn't provide any sensible way to enforce type class properties on implementations.
To stick with "the least amount of overhead" paradigm type classes implemented atop of Elixir protocols.
## Installation
The package can be installed by adding `bindable` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:bindable, "~> 1.1.0"}
]
end
```
The docs can be found at <https://hexdocs.pm/bindable>.