Search code examples
elixirpipelineenumerable

Elixir Enum.map piped to Enum.filter not working


I'm trying to write a tool that will take a list of files/folders from File.ls, add the full path to them, and filter them based on if they are directories or not.

This works:

directory_contents = File.ls!(directory_path)

contents_with_full_paths = Enum.map directory_contents, fn(item) -> Path.join(directory_path, item) end
only_dirs = Enum.filter contents_with_full_paths, fn(item) -> File.dir?(item) end

This does not:

directory_contents = File.ls!(directory_path)

directory_contents
|> Enum.map fn(item) -> Path.join(directory_path, item) end
|> Enum.filter fn(item) -> File.dir?(item) end

It throws

** (Protocol.UndefinedError) protocol Enumerable not implemented for #Function<1.10194857/1 in RepoFinder.subdirectories_in/1>, only anonymous functions of arity 2 are enumerable. This protocol is implemented for: Date.Range, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Range, Stream
    (elixir) lib/enum.ex:3213: Enumerable.Function.reduce/3
    (elixir) lib/enum.ex:1847: Enum.filter/2

Why is that? Shouldn't those two implementations work essentially the same way?


Solution

  • When the code was compiled, the compiler threw a detailed warning explaining what’s wrong with this code:

    warning: parentheses are required when piping into a function call. For example:

    foo 1 |> bar 2 |> baz 3
    

    is ambiguous and should be written as

    foo(1) |> bar(2) |> baz(3)
    

    Ambiguous pipe found at:
        /path/to/file.ex:line

    That said, with pipe operator, it is mandatory to use parentheses around arguments to function calls:

    directory_contents
    |> Enum.map(fn(item) -> Path.join(directory_path, item) end)
    |> Enum.filter(&File.dir?/1)
    

    Otherwise, the compiler uses wrong operator precedence.