README.md

# CredoUnnecessaryReduce

From the docs for `Enum.reduce/3`:

> When an operation cannot be expressed by any of the functions in the Enum module, developers will most likely resort to reduce/3.

-----

This library implements a custom [credo](https://github.com/rrrene/credo) check which looks for opportunities to refactor usage of `Enum.reduce` into other `Enum` functions (and `Map.new`!).

The goal is both to:

* identify places where code could be simpler
* help people learn about variety of useful functions that exist in Elixir's `Enum` module

See also [Motivation](https://github.com/cheerfulstoic/credo_unnecessary_reduce/wiki/Motivation) in the wiki.

## Examples

For example, the following cases would be detected:

```elixir
Enum.reduce(numbers, [], fn i, result -> [i * 10 | result] end)
|> Enum.reverse()

# The `++` is another way to build a list in Elixir, but because of the way lists are 
# stored, it's much more efficient to prepend and then reverse.
Enum.reduce(numbers, [], fn i, result -> result ++ [i * 10] end)
```

Both of these cases could be replaced by a `Enum.map(numbers, &(&1 * 10))`

For a different example:

```elixir
Enum.reduce(numbers, [], fn number, result ->
  if rem(number, 2) != 0 do
    result
  else
    [number | result]
  end
end)
|> Enum.reverse()
```

That whole block could be replaced by `Enum.filter(numbers, &(rem(number, 2) == 0))`.

Currently this library checks for cases of `Enum.reduce` which could be replaced by:

* `Enum.any?`
* `Enum.all?`
* `Enum.filter`
* `Enum.flat_map`
* `Enum.map`
* `Enum.product`
* `Enum.product_by`
* `Enum.reject`
* `Enum.sum`
* `Enum.count`
* `Map.new`

See [Examples](https://github.com/cheerfulstoic/credo_unnecessary_reduce/wiki/Examples) for specific cases of code that is detected.

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `credo_unnecessary_reduce` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:credo_unnecessary_reduce, "~> 0.1.0"}
  ]
end
```

Then you should add `{CredoUnnecessaryReduce.Check, []},` to your `.credo.exs` file under the `enabled` section.

## Combination cases

Sometimes an `Enum.reduce` call will be doing more than one thing at a time.  For example it's very common to have a `reduce` can be replaced by an `Enum.filter` piped into an `Enum.map`:

```elixir
Enum.reduce(numbers, [], fn item, acc ->
  if rem(item, 2) == 0 do
    [item * 2 | acc]
  else
    acc
  end
end)
|> Enum.reverse()
```

The split out version:

```elixir
numbers
|> Enum.filter(& rem(&1, 2) == 0)
|> Enum.map(& &1 * 2)
```

In this case the `credo_unnecessary_reduce` check will just recommend replacing with the outermost pattern in can detect (`Enum.filter` in this case).  It may be possible to support suggesting the whole chain, but because of the complexity of that challenge it's enough for now that `Enum.reduce` calls that can be simplified are identified successfully.

## TODO

Potential low hanging fruit:

* `Enum.count_until`
* `Enum.frequencies`
* `Enum.frequencies_by`
* `Enum.max`
* `Enum.max_by`
* `Enum.min`
* `Enum.min_by`

Unsure right now, could be good:

* `Enum.find`
* `Enum.find_index`
* `Enum.find_value`
* `Enum.group_by`
* `Enum.into`
* `Enum.join`
* `Enum.min_max`
* `Enum.min_max_by`
* `Enum.uniq`
* `Enum.uniq_by`

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at <https://hexdocs.pm/credo_unnecessary_reduce>.