Skip to content

Rewrite some Enum.reduce when they can be expressed with a standard library functions - #1

Open
katafrakt wants to merge 3 commits into
jumpfrom
rewrite-reduces
Open

Rewrite some Enum.reduce when they can be expressed with a standard library functions#1
katafrakt wants to merge 3 commits into
jumpfrom
rewrite-reduces

Conversation

@katafrakt

Copy link
Copy Markdown
Collaborator

No description provided.

@s3cur3 s3cur3 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amaaaaazing! 🤩

Comment thread lib/style/single_node.ex
Comment on lines +380 to +383
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love the reduce_rewrite/2 helper!

Comment thread lib/style/single_node.ex
Comment on lines +787 to +794
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)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So clean!

Comment thread lib/style/single_node.ex
# `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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could potentially combine the two if statements within this function:

Suggested change
identity = if op == :+, do: 0, else: 1
{identity, name} = if op == :+, do: {0, :sum_by}, else: {1, :product_by}

Comment thread lib/style/single_node.ex

defp simplify_reduce(_init, _reducer), do: nil

defp reduce_rewrite(init, reducer) do

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread test/style/pipes_test.exs
Comment on lines +1927 to +1930
expected =
"a |> Enum.filter(&((&1 > 0 and &1 < 10) && &1 != 5)) |> bar()"
|> Code.format_string!()
|> IO.iodata_to_binary()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, smart!

Comment on lines +1592 to +1594
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably remove this one, since it's covered by another existing test, right?

Comment on lines +1548 to +1552
assert_style(
"Enum.reduce(items, 0, fn item, acc -> acc + item.count end)",
"Enum.sum_by(items, fn item -> item.count end)"
)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Comment on lines +1657 to +1669
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good tests!

Comment on lines +1684 to +1686
test "leaves the unconditional acc + 1 form" do
assert_style("Enum.reduce(items, 0, fn item, acc -> acc + 1 end)")
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1718 to +1724
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are nice for documentation of future followup opportunities. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants