Strange error with nested Enum.reduce_while

For the following code

      c = %{a: %{}, b: [%{}, %{}]}

      r =
        Enum.reduce_while(c, nil, fn {_k, v}, _acc ->
          case v do
            %{} ->
              {:cont, nil}

            [%{} | _] = list ->
              Enum.reduce_while(list, nil, fn l, _acc ->
                {:cont, nil}
              end)
          end
        end)

The two reduce_while should both end with {:cont, nil} and r should be nil in the end.

However, I got an FunctionClauseError instead:

** (FunctionClauseError) no function clause matching in Enumerable.List.reduce/3    
    
    The following arguments were given to Enumerable.List.reduce/3:
    
        # 1
        []
    
        # 2
        nil
    
        # 3
        #Function<41.105768164/2 in :erl_eval.expr/6>
    
    Attempted function clauses (showing 4 out of 4):
    
        def reduce(_list, {:halt, acc}, _fun)
        def reduce(list, {:suspend, acc}, fun)
        def reduce([], {:cont, acc}, _fun)
        def reduce([head | tail], {:cont, acc}, fun)
    
    (elixir 1.16.0) lib/enum.ex:4839: Enumerable.List.reduce/3
    (elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
    iex:4: (file)

The error message says the 2nd argument is nil (therefore, cannot match any clause). However, this should not be possible. Because a :cont tuple is guaranteed:

Any idea why it happens? Where does the bug locate, in my code, the standard lib, or even deeper?

The inner reduce_while here will unwrap {:cont, nil} and return nil.

2 Likes

Ah, yes. You got my blindspot! I didn’t think in this direction. I though this error is triggered when first entry, which led me to an wrong end. Thank you for saving my day!