keep_zen

keep_zen

Why did not optimize `Enum.map`s chain

For a functor, map(map(functor, fun), fun2) equal as map(functor, fun2*fun).
In Elixir, I think Enum module is a functor, but |> did not optimize it.
Like this:

defmodule A do
   def add1(e) do
     IO.puts("+1")
    e+1
  end
 def sub1(e) do
   IO.puts("-1")
  e-1
 end
end
1..10
|> Enum.map(&A.add1/1)
|>Enum.map(&A.sub1/1)

The console outputs like:

+1
+1
+1
...
-1
-1
-1
...
-1

If the code have optimzed it should show as:

+1
-1
+1
-1
...

Redefine |>, I think I can do the optimation for Enum.maps. If there is functors’ list, optimize for all functors’ chains,can also be done. I meaning, It not hard to do. But Why it is not happend in Elixir?

Marked As Solved

krstfk

krstfk

There’s a lot of confusion here.
Functors (and the likes) are an abstraction that helps to reason about some compositions, they’re not magic.
So what is a functor (in programming):
It’s a data structure, with a (f)map function that has the following properties :

    fmap id = id
    fmap (f . g)  ==  fmap f . fmap g

That’s it those are the rules of a functor.
So, is a list, under Enum.map a functor? No. Because, elixir/erlang/ocaml/scala… allow for side effects.
Namely, if you were to write:

f = fn x -> 
   IO.puts(x)
   x + 1
g = fn x ->
   IO.puts(x)
  x * 2

Now let’s try this :

iex(60)> Enum.map([1,2,3], fn x -> f.(g.(x)) end )
1
2
2
4
3
6
[3, 5, 7]
iex(61)> Enum.map(Enum.map([1,2,3], g), f)
1
2
3
2
4
6
[3, 5, 7]

So plainly, list under map is not a functor, unless you ignore the side effects. But if you guarantee that f and g are side effects free, list under map is a functor.

Now, nowhere in the functor laws is it specified that one side of the equation should be favoured over the other side.

What you’re talking about is something called fusion, or operations fusion or shortcut fusion or stream fusion. These have little to do with functors (at most, functor laws may allow you to prove that these optimisations are safe), and they are not ‘not hard’. Take a look at the map implementation for list in Haskell, look at the rewriting rules. Also, note that those optimisations don’t apply for all instances of functor (check fmap for Maybe).

Operations fusions, come with some sort of trade off, and if you have long chains of operations, maybe use Stream.map instead of Enum.map, but I don’t know, benchmark (more on that later). (BTW ops fusion are not that common).

Regarding the pipe operator (|>) vs functional composition, the main difference is wether you read left to right or right to left (at least in strict languages). Ocaml doesn’t have an infix composition operator either. Again, this is orthogonal to compiler optimisations. But, you may implement it trivially:

defmodule Compose
   # DO NOT DO THAT
   defp compose(f,g), do: fn x -> f.(g.(x)) end
   def f <~> g, do: compose(f, g)
end
iex(2)> import Compose
Compose
iex(3)> test = fn x -> x + 1 end <~> fn x -> x * 2 end
#Function<0.15966921/1 in Compose.compose/2>
iex(4)> test.(1)
3

It’s not that different from the pipe operator (except you read it in the other direction, and it’s a bit more fiddly with anonymous functions):

iex(16)> test = fn x -> x  |> then(fn x -> x * 2 end) |> then(fn x -> x + 1 end)  end
#Function<44.65746770/1 in :erl_eval.expr/5>
iex(17)> test.(1)
3

TLDR; : don’t worry about functional abstractions, you probably don’t need them. Learn the standard library. Then learn OTP abstraction.

( Final note : optimisation
In general we have good frameworks for getting a sense of software complexity. However, for mere mortals, the only way to actually optimise for one’s use case is benchmarking. Put another way constant factors may be extremely important regardless of O)

Also Liked

dorgan

dorgan

Enum is not a functor, it always goes from enumerable to lists. It’s also a set of eager funcions, the funcional composition you assume is not used here nor is it common in Elixir.

If you want to consider it a functor, you need to apply its properties yourself, Elixir won’t do it for you. But know that functors as a concept dont exist in elixir the way they exist in Haskell or Ocaml.

I also have to note that the Pipe operator just pipes function calls like the unix Pipe does, it’s not a functional composition operator.

LostKobrakai

LostKobrakai

You could use Stream.map, which will apply the mapping functions lazily.

LostKobrakai

LostKobrakai

I’d strongly suggest actually running benchmarks. Just because you can inline things doesn’t mean that the results will be more performant.

Where Next?

Popular in Questions Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New

Other popular topics Top

gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

We're in Beta

About us Mission Statement