View Source Enumancer (Enumancer v0.0.3)

Macros to effortlessly define highly optimized Enum pipelines

overview

Overview

Enumancer provides a defenum/2 macro, which will convert a pipeline of Enum function calls to an optimized tail-recursive function.

defmodule BlazingFast do
  import Enumancer

  defenum sum_squares(numbers) do
    numbers
    |> map(& &1 * &1)
    |> sum()
  end
end

1..10_000_000 |> BlazingFast.sum_squares()  # very fast
1..10_000_000 |> Enum.map(& &1 * &1) |> Enum.sum()  # super slow
1..10_000_000 |> Stream.map(& &1 * &1) |> Enum.sum()  # super slow

There is no need to add Enum., map/2 will be interpreted as Enum.map/2 within defenum/2.

In order to see the actual functions that are being generated, you can just replace defenum/2 by defenum_explain/2 and the code will be printed in the console.

The defenum_explain/2 approach can be useful if you don't want to take the risk of using Enumancer and macros in your production code, but it can inspire the implementation of your optimized recursive functions.

available-functions

Available functions

Most functions taking an Enumerable and returning a list can be used anywhere in the pipeline (e.g. map/2, filter/2, with_index/2...).

On the other hand, functions taking an Enumerable and returning some non-list accumulator (e.g. sum/1, join/2, max/1...) can only be used at the end of the pipeline. There are other cases like sort/1 or reduce/1 which cannot work without the full list and are also limited to the end of the pipeline.

Functions that need to stop without reducing the Enumerable completely, such as take/2 or any?/1, are not available at this point, but might be implemented in the future.

Also, please note that many functions from the Enum module are accepting optional callbacks to add an extra map or filter step. By design, Enumancer does not implement these. For a very simple reason: the available primitives can be combined at will to reproduce them, without any runtime overhead.

See examples below:

replacing-some-composed-functions

Replacing some "composed" functions

  • Instead of |> map_join("-", fun), just use |> map(fun) |> join("-")
  • Instead of |> map_intersperse(sep, fun), just use |> map(fun) |> intersperse(sep)
  • Instead of |> count(&has_valid_foo?/1), just use |> filter(&has_valid_foo?/1) |> count()
  • Instead of |> with_index(fn x, i -> foo(x, i) end), just use |> with_index() |> map(fn {x, i} -> foo(x, i) end)
  • Instead of |> Map.new(fn x -> {x.k, x.v} end), just use |> map(fn x -> {x.k, x.v} end) |> Map.new()

anywhere-in-the-pipeline

Anywhere in the pipeline

|> map_reduce(acc, fun) by itself returns a tuple and cannot be piped any further.

But |> map_reduce(acc, fun) |> elem(0) can be piped if you only need the mapped list.

only-at-the-end-of-the-pipeline

Only at the end of the pipeline

Link to this section Summary

Functions

A macro transforming a pipeline of Enum transformations to an optimized recursive function at compile time.

Same as defenum/2, but will print the generated code in the console.

Link to this section Functions

Link to this macro

defenum(head, list)

View Source (macro)

A macro transforming a pipeline of Enum transformations to an optimized recursive function at compile time.

See Enumancer documentation for available functions.

examples

Examples

defmodule BlazingFast do
  import Enumancer

  defenum sum_odd_squares(numbers) do
    numbers
    |> filter(&rem(&1, 2) == 1)
    |> map(& &1 * &1)
    |> sum()
  end
end
Link to this macro

defenum_explain(head, list)

View Source (macro)

Same as defenum/2, but will print the generated code in the console.

Useful for debug or learning purpose.