diff --git a/lib/elixir/builtin.ex b/lib/elixir/builtin.ex index 37fded8856..36684dad02 100644 --- a/lib/elixir/builtin.ex +++ b/lib/elixir/builtin.ex @@ -884,6 +884,64 @@ defmodule Elixir.Builtin do quote do: if(!unquote(clause), unquote(options)) end + @doc """ + Allows you to destructure two lists, assigning each + term in the right to the left. Differently from pattern + matching via `=`, if the sizes of the left and right + lists don't match,, structuring simply stops instead + of raising an error. + + ## Examples + + destructure [x,y,z], [1,2,3,4,5] + x #=> 1 + y #=> 2 + z #=> 3 + + Notice in the example above, even though the right + size has more entries than the left, structuring works + fine. If the right size is smaller, the remaining items + are simply assigned to nil: + + destructure [x,y,z], [1] + x #=> 1 + y #=> nil + z #=> nil + + The left side supports any expression you would use + on the left side of a match: + + x = 1 + destructure [^x,y,z], [1,2,3] + + The example above will only work if x matches + the first value from the right side. + """ + defmacro destructure([head|tail], right) do + # For the first match, we need to ensure the element given + # is an array. For the other matches, we don't care because + # we have validated it is a list. + head = quote do + case unquote(right) do + match: [unquote(head)|t] + t + match: [] + unquote(head) = nil + end + end + + List.foldl tail, head, fn(item, acc) -> + quote do + case unquote(acc) do + match: [unquote(item)|t] + t + match: _ + unquote(item) = nil + end + end + end + end + @doc """ Concatenates two binaries. diff --git a/test/elixir/kernel/destructure_test.exs b/test/elixir/kernel/destructure_test.exs new file mode 100644 index 0000000000..f13fc51d9d --- /dev/null +++ b/test/elixir/kernel/destructure_test.exs @@ -0,0 +1,49 @@ +Code.require_file "../../test_helper", __FILE__ + +defmodule Kernel.DestructureTest do + use ExUnit.Case + + test :less do + destructure [x,y,z], [1,2,3,4,5] + assert_equal 1, x + assert_equal 2, y + assert_equal 3, z + end + + test :more do + destructure [a,b,c,d,e], [1,2,3] + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + assert_equal nil, d + assert_equal nil, e + end + + test :equal do + destructure [a,b,c], [1,2,3] + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + end + + test :none do + destructure [a,b,c], [] + assert_equal nil, a + assert_equal nil, b + assert_equal nil, c + end + + test :match do + destructure [1,b,_], [1,2,3] + assert_equal 2, b + end + + test :invalid_match do + a = 3 + assert_raises MatchError, fn -> + destructure [^a,b,c], a_list + end + end + + defp a_list, do: [1,2,3] +end