Search code examples
elixir

FunctionClauseError when passing :discard to Enum.chunk_every


So i have been doing a udemy course to learn Elixir, where i am suppose to take list and create new list that contains a lists of values in length of 3

So this is my old list

[23, 137, 4, 86, 54, 223, 37, 191, 22, 167, 25, 238, 102, 56, 39, 4]

and i want to convert it to this

[[23, 137, 4],[ 86, 54, 223],[ 37, 191, 22],[ 167, 25, 238],[ 102, 56, 39]]

My code

  def main(name) do
    name
    |> hash
    |> pickColor
    |> buildGrid
  end

  def buildGrid(%Identicon.Image{hex: hex}=image) do
    hex
    |>Enum.chunk_every(3, :discard)
  end

  def pickColor(%Identicon.Image{hex: [r,g,b | _tail]} = image) do
     %Identicon.Image{image | color: {r,g,b}}
  end

  def hash(name) do
    hex = :crypto.hash(:md5,name)
    |> :binary.bin_to_list()
    %Identicon.Image{hex: hex}
  end

the response im getting is

** (FunctionClauseError) no function clause matching in Enum.chunk_every/4

    The following arguments were given to Enum.chunk_every/4:

        # 1
        [23, 137, 4, 86, 54, 223, 37, 191, 22, 167, 25, 238, 102, 56, 39, 4]

        # 2
        3

        # 3
        :discard

        # 4
        []

    Attempted function clauses (showing 1 out of 1):

        def chunk_every(enumerable, count, step, leftover) when is_integer(count) and count > 0 and is_integer(step) and step > 0

    (elixir 1.15.0) lib/enum.ex:547: Enum.chunk_every/4
    (identicon 0.1.0) lib/identicon.ex:23: Identicon.buildGrid/1
    iex:6: (file)

Solution

  • You are doing this:

    iex> Enum.chunk_every(list, 3, :discard)
    ** (FunctionClauseError) no function clause matching in Enum.chunk_every/4
    

    But you need to do this:

    iex> Enum.chunk_every(list, 3, 3, :discard)
    [[23, 137, 4], [86, 54, 223], [37, 191, 22], [167, 25, 238], [102, 56, 39]]
    

    There are actually three forms of Enum.chunk_every:

    1. Two-argument form: Enum.chunk_every/2, is a shortcut to the three argument argument form: chunk_every(list, count)chunk_every(list, count, count), where the count is also passed as the third argument, step.
    2. Three-argument form: Enum.chunk_every/4, is actually the four-argument form where the default value of the fourth argument, leftover, is the empty list: chunk_every(list, count, step)chunk_every(list, count, step, []). This differs from the two-argument form, because step is not optional.
    3. The four-argument form, same as the three-argument form but with an explicit value for leftover.

    Your code is doing Enum.chunk_every(enum, 3, :discard), attempting to pass :discard as the fourth leftover argument, but without passing the required third step argument. So Elixir is correctly complaining that there is no version of this function that takes (list, integer, atom) arguments. The fix is to pass the required third argument step, with the same value as count.

    It's confusing, because the two-argument version allows the step to default to count automatically (and by extension, leftover to default to [] automatically), but the four-argument version requires that you pass step explicitly, while still allowing the leftover argument to be optional.