you'd better to take the warning: 'parentheses are required when piping into a function call.'

September 6, 2017    Elixir Pipe

Guess what! What’s happening to following code?

"./fib.exs"
|> File.read!
|> String.split ~r/(?<h>)fib(?<t>)/, on: [:h, :t]
|> Enum.count fn word -> word == "fib" end

Probably you know what will happen already if you keep up Elixir.

But I didn’t know just now and I was a little stuck in it.





Have you made up your mind?





If you have no clue, you’d better to take the warning: parentheses are required when piping into a function call.





For the reason that it prevents you from wasting your time.

Let me explain quickly. The fib.exs is just a elixir code for calculating Finonacci sequence and above code try to count how many times the word: fib, appears in fib.exs.

Returning the number is expected in these operations but in this case this code cause an argument error.

Like this

** (ArgumentError) argument error
    (stdlib) re.erl:734: :re.run("defmodule FibSolver do\n  def fib(scheduler) do\n    send scheduler, { :ready, self }\n    receive do\n      { :fib, n, client } ->\n        send client, { :answer, n, fib_calc(n), self}\n        fib(scheduler)\n      { :shutdown } ->\n        exit(:normal)\n    end\n  end\n\n  defp fib_calc(0), do: 0\n  defp fib_calc(1), do: 1\n  defp fib_calc(n), do: fib_calc(n-1) + fib_calc(n-2)\nend\n\ndefmodule Scheduler do\n  def run(num_processes, module, func, to_calculate) do\n    (1..num_processes)\n    |> Enum.map(fn(_) -> spawn(module, func, [self]) end)\n    |> schedule_processes(to_calculate, [])\n  end\n\n  def schedule_processes(processes, queue, results) do\n    receive do\n      {:ready, pid} when length(queue) > 0 ->\n        [next | tail] = queue\n        send pid, {:fib, next, self}\n        schedule_processes(processes, tail, results)\n      {:ready, pid} ->\n        send pid, { :shutdown }\n        if length(processes) > 1 do\n          schedule_processes(List.delete(processes, pid), queue, results)\n        else\n          Enum.sort(results, fn {n1, _}, {n2, _} -> n1 <= n2 end)\n        end\n      {:answer, number, result, _pid} ->\n        schedule_processes(processes, queue, [ {number, result} | results])\n    end\n  end\nend\n\nto_process = [37, 37, 37, 37, 37, 37]\nEnum.each 1..10, fn num_processes ->\n  {time, result} = :timer.tc(\n    Scheduler, :run,\n    [num_processes, FibSolver, :fib, to_process]\n  )\n\n  if num_processes == 1 do\n    IO.puts inspect(result)\n    IO.puts \"\\n # time (s)\"\n  end\n  :io.format \"~2B       ~.2f~n\", [num_processes, time/1_000_000.0]\nend\n", {:re_pattern, 2, 0, 0, <<69, 82, 67, 80, 101, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 116, 0, 0, 0, 2, 0, 0, 0, 64, 0, 4, 0, 2, 0, 0, 0, 0, 0, 0, 0, ...>>}, [:global, {:capture, 0}])
    (elixir) lib/regex.ex:467: Regex.split/3

Before we look at why it happens, I want to claim that if you say “hmmm”, I recommend you to fix warning place actively.

It’s hard to see where is wrong from the error right?

Pipe misinterpret the code

As the title imply, pipe parentheses have something to do with the error. Misinterpreting leads to the error

Let’s take a look to start iex

iex(5)> File.read! "./fib.exs"
"defmodule FibSolver do\n  def fib(scheduler) do\n    send scheduler, { :ready, self }\n    receive do\n      { :fib, n, client } ->\n        send client, { :answer, n, fib_calc(n), self}\n        fib(scheduler)\n      { :shutdown } ->\n        exit(:normal)\n    end\n  end\n\n  defp fib_calc(0), do: 0\n  defp fib_calc(1), do: 1\n  defp fib_calc(n), do: fib_calc(n-1) + fib_calc(n-2)\nend\n\ndefmodule Scheduler do\n  def run(num_processes, module, func, to_calculate) do\n    (1..num_processes)\n    |> Enum.map(fn(_) -> spawn(module, func, [self]) end)\n    |> schedule_processes(to_calculate, [])\n  end\n\n  def schedule_processes(processes, queue, results) do\n    receive do\n      {:ready, pid} when length(queue) > 0 ->\n        [next | tail] = queue\n        send pid, {:fib, next, self}\n        schedule_processes(processes, tail, results)\n      {:ready, pid} ->\n        send pid, { :shutdown }\n        if length(processes) > 1 do\n          schedule_processes(List.delete(processes, pid), queue, results)\n        else\n          Enum.sort(results, fn {n1, _}, {n2, _} -> n1 <= n2 end)\n        end\n      {:answer, number, result, _pid} ->\n        schedule_processes(processes, queue, [ {number, result} | results])\n    end\n  end\nend\n\nto_process = [37, 37, 37, 37, 37, 37]\nEnum.each 1..10, fn num_processes ->\n  {time, result} = :timer.tc(\n    Scheduler, :run,\n    [num_processes, FibSolver, :fib, to_process]\n  )\n\n  if num_processes == 1 do\n    IO.puts inspect(result)\n    IO.puts \"\\n # time (s)\"\n  end\n  :io.format \"~2B       ~.2f~n\", [num_processes, time/1_000_000.0]\nend\n"

Yes, File.read! works. It returns binary.

Once we’ll pass it to String.split, we expect the list which value is splited at space

As document says

iex(6)> h String.split


                               def split(binary)

Divides a string into substrings at each Unicode whitespace occurrence with
leading and trailing whitespace ignored. Groups of whitespace are treated as a
single occurrence. Divisions do not occur on non-breaking whitespace.

## Examples

    iex> String.split("foo bar")
    ["foo", "bar"]

    iex> String.split("foo" <> <<194, 133>> <> "bar")
    ["foo", "bar"]

    iex> String.split(" foo   bar ")
    ["foo", "bar"]

    iex> String.split("no\u00a0break")
    ["no\u00a0break"]

What actually returns without parentheses? Let’s invoke

iex(6)> File.read! "./fib.exs" |> String.split

Actually this style is not preferred from another point, but let me ignore it Retrun is

"defmodule FibSolver do\n  def fib(scheduler) do\n    send scheduler, { :ready, self }\n    receive do\n      { :fib, n, client } ->\n        send client, { :answer, n, fib_calc(n), self}\n        fib(scheduler)\n      { :shutdown } ->\n        exit(:normal)\n    end\n  end\n\n  defp fib_calc(0), do: 0\n  defp fib_calc(1), do: 1\n  defp fib_calc(n), do: fib_calc(n-1) + fib_calc(n-2)\nend\n\ndefmodule Scheduler do\n  def run(num_processes, module, func, to_calculate) do\n    (1..num_processes)\n    |> Enum.map(fn(_) -> spawn(module, func, [self]) end)\n    |> schedule_processes(to_calculate, [])\n  end\n\n  def schedule_processes(processes, queue, results) do\n    receive do\n      {:ready, pid} when length(queue) > 0 ->\n        [next | tail] = queue\n        send pid, {:fib, next, self}\n        schedule_processes(processes, tail, results)\n      {:ready, pid} ->\n        send pid, { :shutdown }\n        if length(processes) > 1 do\n          schedule_processes(List.delete(processes, pid), queue, results)\n        else\n          Enum.sort(results, fn {n1, _}, {n2, _} -> n1 <= n2 end)\n        end\n      {:answer, number, result, _pid} ->\n        schedule_processes(processes, queue, [ {number, result} | results])\n    end\n  end\nend\n\nto_process = [37, 37, 37, 37, 37, 37]\nEnum.each 1..10, fn num_processes ->\n  {time, result} = :timer.tc(\n    Scheduler, :run,\n    [num_processes, FibSolver, :fib, to_process]\n  )\n\n  if num_processes == 1 do\n    IO.puts inspect(result)\n    IO.puts \"\\n # time (s)\"\n  end\n  :io.format \"~2B       ~.2f~n\", [num_processes, time/1_000_000.0]\nend\n"
# result

Nothing happens… Use IO.inspect to check what is pass through the pipe

iex(9)> File.read! "./fib.exs" |> IO.inspect
"./fib.exs" ## HERE!!
"defmodule FibSolver do\n  def fib(scheduler) do\n    send scheduler, { :ready, self }\n    receive do\n      { :fib, n, client } ->\n        send client, { :answer, n, fib_calc(n), self}\n        fib(scheduler)\n      { :shutdown } ->\n        exit(:normal)\n    end\n  end\n\n  defp fib_calc(0), do: 0\n  defp fib_calc(1), do: 1\n  defp fib_calc(n), do: fib_calc(n-1) + fib_calc(n-2)\nend\n\ndefmodule Scheduler do\n  def run(num_processes, module, func, to_calculate) do\n    (1..num_processes)\n    |> Enum.map(fn(_) -> spawn(module, func, [self]) end)\n    |> schedule_processes(to_calculate, [])\n  end\n\n  def schedule_processes(processes, queue, results) do\n    receive do\n      {:ready, pid} when length(queue) > 0 ->\n        [next | tail] = queue\n        send pid, {:fib, next, self}\n        schedule_processes(processes, tail, results)\n      {:ready, pid} ->\n        send pid, { :shutdown }\n        if length(processes) > 1 do\n          schedule_processes(List.delete(processes, pid), queue, results)\n        else\n          Enum.sort(results, fn {n1, _}, {n2, _} -> n1 <= n2 end)\n        end\n      {:answer, number, result, _pid} ->\n        schedule_processes(processes, queue, [ {number, result} | results])\n    end\n  end\nend\n\nto_process = [37, 37, 37, 37, 37, 37]\nEnum.each 1..10, fn num_processes ->\n  {time, result} = :timer.tc(\n    Scheduler, :run,\n    [num_processes, FibSolver, :fib, to_process]\n  )\n\n  if num_processes == 1 do\n    IO.puts inspect(result)\n    IO.puts \"\\n # time (s)\"\n  end\n  :io.format \"~2B       ~.2f~n\", [num_processes, time/1_000_000.0]\nend\n"

Misinterpreting here! First argument is passed to second function and then first function is invoked.

This is the fact that compiler does…

File.read!("./fib.exs" |> IO.inspect)

So result of accepting warning is

iex(7)> File.read!("./fib.exs") |> String.split
["defmodule", "FibSolver", "do", "def", "fib(scheduler)", "do", "send",
 "scheduler,", "{", ":ready,", "self", "}", "receive", "do", "{", ":fib,", "n,",
 "client", "}", "->", "send", "client,", "{", ":answer,", "n,", "fib_calc(n),",
 "self}", "fib(scheduler)", "{", ":shutdown", "}", "->", "exit(:normal)", "end",
 "end", "defp", "fib_calc(0),", "do:", "0", "defp", "fib_calc(1),", "do:", "1",
 "defp", "fib_calc(n),", "do:", "fib_calc(n-1)", "+", "fib_calc(n-2)", "end",
 "defmodule", "Scheduler", "do", "def", "run(num_processes,", "module,",
 "func,", "to_calculate)", "do", "(1..num_processes)", "|>", "Enum.map(fn(_)",
 "->", "spawn(module,", "func,", "[self])", "end)", "|>",
 "schedule_processes(to_calculate,", "[])", "end", "def",
 "schedule_processes(processes,", "queue,", "results)", "do", "receive", "do",
 "{:ready,", "pid}", "when", "length(queue)", ">", "0", "->", "[next", "|",
 "tail]", "=", "queue", "send", "pid,", "{:fib,", "next,", "self}",
 "schedule_processes(processes,", "tail,", "results)", "{:ready,", "pid}", ...]

It works!

Conclusion

I got a little bit trouble because I thought warning is not crucial.

If you have been taking it lightly like me, you’d better to fix it :)



comments powered by Disqus