/bindable

Elixir for-comprehension that goes beyond the lists

Primary LanguageElixirMIT LicenseMIT

Bindable

Elixir CI

Elixir for-comprehension that goes beyond the Lists.

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.
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:

def deps do
  [
    {:bindable, "~> 1.1.0"}
  ]
end

The docs can be found at https://hexdocs.pm/bindable.