Rewrite some Enum.reduce when they can be expressed with a standard library functions - #1
Rewrite some Enum.reduce when they can be expressed with a standard library functions#1katafrakt wants to merge 3 commits into
Conversation
b89f13d to
7635bc9
Compare
| case reduce_rewrite(init, reducer) do | ||
| :sum -> {{:., dm, [{:__aliases__, am, [:Enum]}, :sum]}, m, [enum]} | ||
| {module, name, new_reducer} -> {{:., dm, [{:__aliases__, am, module}, name]}, m, [enum, new_reducer]} | ||
| :none -> node |
| with true <- sum_product_by_available?(), | ||
| true <- literal?(init, identity), | ||
| {:ok, expr} <- accumulator_partner(a, b, acc), | ||
| false <- references_var?(expr, acc), | ||
| false <- same_variable?(expr, item), | ||
| false <- literal_constant?(expr) do | ||
| name = if op == :+, do: :sum_by, else: :product_by | ||
| {{[:Enum], name}, rebuild.(expr)} |
| # `Enum.reduce(enum, 0, fn item, acc -> acc + expr end)` => `Enum.sum_by(enum, fn item -> expr end)` | ||
| # `Enum.reduce(enum, 1, fn item, acc -> acc * expr end)` => `Enum.product_by(enum, fn item -> expr end)` | ||
| defp classify_reduce(init, item, acc, _stmts, {op, _, [a, b]}, rebuild) when op in [:+, :*] do | ||
| identity = if op == :+, do: 0, else: 1 |
There was a problem hiding this comment.
We could potentially combine the two if statements within this function:
| identity = if op == :+, do: 0, else: 1 | |
| {identity, name} = if op == :+, do: {0, :sum_by}, else: {1, :product_by} |
|
|
||
| defp simplify_reduce(_init, _reducer), do: nil | ||
|
|
||
| defp reduce_rewrite(init, reducer) do |
There was a problem hiding this comment.
Thoughts on pulling out the ~200 lines of reduce classifier code out into a new helper module? It'd kind of be nice from an organizational perspective, but I probably wouldn't bother trying to test it in isolation (that is, apart from the actual rewriting pipeline).
| expected = | ||
| "a |> Enum.filter(&((&1 > 0 and &1 < 10) && &1 != 5)) |> bar()" | ||
| |> Code.format_string!() | ||
| |> IO.iodata_to_binary() |
| test "leaves the bare acc + item case for the Enum.sum rewrite" do | ||
| assert_style("Enum.reduce(items, 0, fn item, acc -> acc + item end)", "Enum.sum(items)") | ||
| end |
There was a problem hiding this comment.
We can probably remove this one, since it's covered by another existing test, right?
| assert_style( | ||
| "Enum.reduce(items, 0, fn item, acc -> acc + item.count end)", | ||
| "Enum.sum_by(items, fn item -> item.count end)" | ||
| ) | ||
| end |
There was a problem hiding this comment.
Can the expression be anything? For instance, do we rewrite this:
invoice
|> Map.get("items", [])
|> Enum.reduce(0, fn item, acc -> acc + (item["amount"] || 0) end)
| test "leaves a frequency counter whose value reads the accumulator" do | ||
| assert_style("Enum.reduce(items, %{}, fn item, acc -> Map.put(acc, item.k, Map.get(acc, item.k, 0) + 1) end)") | ||
| end | ||
|
|
||
| test "leaves a conditional Map.put (a filter/reject shape)" do | ||
| assert_style( | ||
| "Enum.reduce(items, %{}, fn item, acc -> if item.ok?, do: Map.put(acc, item.k, item.v), else: acc end)" | ||
| ) | ||
| end | ||
|
|
||
| test "leaves a non-empty initial map untouched" do | ||
| assert_style("Enum.reduce(items, %{a: 1}, fn item, acc -> Map.put(acc, item.k, item.v) end)") | ||
| end |
| test "leaves the unconditional acc + 1 form" do | ||
| assert_style("Enum.reduce(items, 0, fn item, acc -> acc + 1 end)") | ||
| end |
There was a problem hiding this comment.
This one could be rewritten to Enum.count(items), right? (I agree if the added value were anything but 1, though, we wouldn't want to touch it.)
No need to make that happen here—might just be a nice followup for some point in the future.
| test "leaves the [expr | acc] prepend form (needs an external Enum.reverse)" do | ||
| assert_style("Enum.reduce(items, [], fn item, acc -> [item.name | acc] end)") | ||
| end | ||
|
|
||
| test "leaves a multi-element append (a flat_map shape)" do | ||
| assert_style("Enum.reduce(items, [], fn item, acc -> acc ++ [item.a, item.b] end)") | ||
| end |
There was a problem hiding this comment.
These are nice for documentation of future followup opportunities. 😄
No description provided.