defmodule ProtocolEx do @moduledoc """ Matcher protocol control module """ defmodule InvalidProtocolSpecification do @moduledoc """ This is raised when a protocol definition is invalid. If a new feature is wanted in the protocol definition, please raise an issue or submit a PR. """ defexception [ast: nil] def message(exc), do: "Unhandled specification node: #{inspect exc.ast}" end defmodule DuplicateSpecification do @moduledoc """ Only one implementation for a given callback per implementaiton is allowed at this time. """ defexception [name: nil, arity: 0] def message(exc), do: "Duplicate specification node: #{inspect exc.name}/#{inspect exc.arity}" end defmodule UnimplementedProtocolEx do @moduledoc """ Somehow a given implementation was consolidated without actually having a required callback specified. """ defexception [proto: nil, name: nil, arity: 0, value: nil] def message(exc), do: "Unimplemented Protocol of `#{exc.proto}` at #{inspect exc.name}/#{inspect exc.arity} of value: #{inspect exc.value}" end defmodule MissingRequiredProtocolDefinition do @moduledoc """ The given implementation is missing a required callback from the protocol. """ defexception [proto: nil, impl: nil, name: nil, arity: -1] def message(exc) do impl = String.replace_prefix(to_string(exc.impl), to_string(exc.proto)<>".", "") "On Protocol `#{exc.proto}` missing a required protocol callback on `#{impl}` of: #{exc.name}/#{exc.arity}" end end defmodule ProtocolExTestFailure do @moduledoc """ A failed test and information about the error condition """ defexception [proto: nil, type: nil, name: nil, meta: nil, value: nil] def message(exc) do location = inspect(exc.meta) "\nOn Protocol `#{exc.proto}`\n\twith type of `#{exc.type}`,\n\tfailed test `#{exc.name}`/#{location}\n\twith error value of: #{inspect exc.value}\n\n" end end defmodule Spec do @moduledoc false defstruct [callbacks: []] end @desc_name :"$ProtocolEx_description$" @desc_attr :protocol_ex_desc @doc """ Define a protocol behaviour. """ defmacro defprotocolEx(name, [do: body]) do parsed_name = get_atom_name(name) # desc_name = get_desc_name(parsed_name) desc_name = get_atom_name_with(name, @desc_name) body = case body do {:__block__, _meta, _lines} = ast -> ast line -> {:__block__, [], [line]} end |> case do {:__block__, meta, lines} -> lines = Enum.map(lines, fn {type, _, _} = ast when type in [:def, :defp] -> ast ast -> Macro.expand(ast, __CALLER__) end) {:__block__, meta, lines} end spec = decompose_spec(body) spec = verify_valid_spec(spec) quote do defmodule unquote(desc_name) do Module.register_attribute(__MODULE__, unquote(@desc_attr), persist: true) @protocol_ex_desc unquote(parsed_name) def spec, do: unquote(Macro.escape(spec)) end end end @doc """ Implement a protocol based on a matcher specification """ defmacro defimplEx(impl_name, matcher, [{:for, for_name} | opts], [do: body]) do name = get_atom_name(for_name) name = __CALLER__.aliases[name] || name desc_name = get_desc_name(name) quote do require unquote(desc_name) ProtocolEx.defimplEx_do(unquote(Macro.escape(impl_name)), unquote(Macro.escape(matcher)), [for: unquote(Macro.escape(name))], [do: unquote(Macro.escape(body))], unquote(opts), __ENV__) end end @doc false def defimplEx_do(impl_name, matcher, [for: name], [do: body], opts, caller_env) do name = get_atom_name(name) desc_name = get_desc_name(name) impl_name = get_atom_name(impl_name) impl_name = get_impl_name(name, impl_name) impl_name = get_atom_name(impl_name) spec = desc_name.spec() test_asts = gen_impl_test_asts(spec) impl_quoted = {:__block__, [], [ quote do def __matcher__, do: unquote(Macro.escape(matcher)) end, quote do def __spec__, do: unquote(desc_name).spec() end, quote do Module.register_attribute(__MODULE__, :protocol_ex, persist: true) end, quote do @protocol_ex unquote(name) end, quote do Module.register_attribute(__MODULE__, :priority, persist: true) end ] ++ case opts[:inline] do nil -> [quote do def __inlined__(_), do: nil end] # :all -> quote do def __inlined__(_), do: true end funs when is_list(funs) -> funs |> Enum.map(fn {fun, arity} -> Macro.prewalk(body, [], fn ({:def, _, [{^fun, _, bindings}, _]} = ast, acc) when length(bindings) === arity -> {ast, [ast | acc]} ({:def, _, [{:when, _, [{^fun, _, bindings}, _]}, _]} = ast, acc) when length(bindings) === arity -> {ast, [ast | acc]} (ast, acc) -> {ast, acc} end) |> case do {_body, ast} -> quote do def __inlined__({unquote(fun), unquote(arity)}), do: unquote(Macro.escape(ast)) end end end) |> List.wrap() |> Enum.reverse([quote do def __inlined__(_), do: nil end]) end ++ List.wrap(body) ++ test_asts } # impl_quoted |> Macro.to_string() |> IO.puts if Code.ensure_loaded?(impl_name) do :code.purge(impl_name) end Module.create(impl_name, impl_quoted, Macro.Env.location(caller_env)) verify_valid_spec_on_module(name, spec, impl_name) end def consolidate_all(opts \\ []) do opts |> get_base_paths() |> Enum.flat_map(fn path -> path |> Path.join("*.beam") |> Path.wildcard() |> case do beam_paths -> beam_paths |> Enum.flat_map(fn path -> # Oh what I'd give for a monadic `do` right about now... >.> case :beam_lib.chunks(path |> to_charlist(), [:attributes]) do {:ok, {_mod, chunks}} -> case get_in(chunks, [:attributes, @desc_attr]) do [proto] -> [proto] _ -> [] end _err -> [] end end) |> case do protocols -> beam_paths |> Enum.flat_map(fn path -> case :beam_lib.chunks(path |> to_charlist(), [:attributes]) do {:ok, {mod, chunks}} -> attributes = chunks[:attributes] case attributes[:protocol_ex] do nil -> [] protos -> protos |> Enum.any?(&Enum.member?(protocols, &1)) |> if do priority = hd(attributes[:priority] || [0]) data = {mod, priority, protos} [data] else [] end end _err -> [] end end) |> case do impls -> protocols |> Enum.map(fn proto_name -> consolidate(proto_name, impls: impls) end) end end end end) end @doc """ Resolve a protocol into a final ready-to-use-module based on already-compiled names sorted by priority """ def consolidate(proto_name, opts \\ []) do impls = case opts[:impls] do nil -> opts |> get_base_paths() |> Enum.flat_map(fn path -> path |> Path.join("*.beam") |> Path.wildcard() end) |> Enum.flat_map(fn path -> case :beam_lib.chunks(path |> to_charlist(), [:attributes]) do {:ok, {mod, chunks}} -> attributes = chunks[:attributes] case attributes[:protocol_ex] do nil -> [] protos -> if Enum.member?(protos, proto_name) do priority = hd(attributes[:priority] || [0]) data = {mod, priority, protos} [data] else [] end end _err -> [] end end) impls -> Enum.filter(impls, &(Enum.member?(elem(&1, 2), proto_name))) end |> Enum.sort_by(fn {name, prio, _} -> {prio, to_string(name)} end, &>=/2) # Sort by priority, then by binary name |> Enum.map(&elem(&1, 0)) proto_desc = Module.concat(proto_name, @desc_name) spec = case proto_desc.spec() do %Spec{} = spec -> spec err -> throw {:invalid_spec, err} end impl_quoted = {:__block__, [], [ quote do def __protocolEx__, do: unquote(Macro.escape(spec)) end | Enum.flat_map(:lists.reverse(spec.callbacks), &load_abstract_from_impls(proto_name, &1, impls)) ] ++ Enum.flat_map(spec.callbacks, &load_test_from_impls(proto_name, &1, impls)) ++ load_tests_from_impls(spec.callbacks) } if Code.ensure_loaded?(proto_name) do :code.purge(proto_name) end Module.create(proto_name, impl_quoted, Macro.Env.location(__ENV__)) if(true, do: proto_name.__tests_pex__([])) proto_name end @doc """ Resolve a protocol into a final ready-to-use-module based on implementation names If priority_sorted is true then it sorts based on the impl priority, else it uses the defined order """ defmacro resolveProtocolEx(orig_name, impls, priority_sorted \\ false) when is_list(impls) do name = get_atom_name(orig_name) name = __CALLER__.aliases[name] || name desc_name = get_desc_name(name) impls = Enum.map(impls, &get_atom_name/1) impls = Enum.map(impls, &get_atom_name_with(name, &1)) impls = Enum.map(impls, &get_atom_name/1) impls = if(priority_sorted, do: Enum.sort_by(impls, &(&1.module_info()[:attributes][:priority] || 0), &>=/2), else: impls) requireds = Enum.map([desc_name | impls], fn req -> {:require, [], [req]} # quote do # require unquote(req) # end end) quote do __silence_alias_warnings__ = unquote(orig_name) unquote_splicing(requireds) ProtocolEx.resolveProtocolEx_do(unquote(name), unquote(impls)) end end @doc false defmacro resolveProtocolEx_do(name, impls) when is_list(impls) do name = get_atom_name(name) desc_name = get_desc_name(name) spec = desc_name.spec() impl_quoted = {:__block__, [], [ quote do def __protocolEx__, do: unquote(Macro.escape(spec)) end | Enum.flat_map(:lists.reverse(spec.callbacks), &load_abstract_from_impls(name, &1, impls)) ] ++ Enum.flat_map(spec.callbacks, &load_test_from_impls(name, &1, impls)) ++ load_tests_from_impls(spec.callbacks) } # impl_quoted |> Macro.to_string() |> IO.puts if Code.ensure_loaded?(name) do :code.purge(name) end Module.create(name, impl_quoted, Macro.Env.location(__CALLER__)) if(true, do: name.__tests_pex__([])) :ok end defp get_base_paths(opts) do case opts[:ebin_root] do nil -> :code.get_path() paths -> List.wrap(paths) end end defp get_atom_name(name) when is_atom(name), do: name defp get_atom_name({:__aliases__, _, names}) when is_list(names), do: Module.concat(names) defp get_atom_name_with(name, at_end) when is_atom(name) and is_atom(at_end), do: {:__aliases__, [alias: false], [name, at_end]} defp get_atom_name_with({:__aliases__, meta, names}, at_end) when is_list(names) and is_atom(at_end), do: {:__aliases__, meta, names ++ [at_end]} defp get_desc_name(name) when is_atom(name), do: Module.concat([name, @desc_name]) defp get_impl_name(name, impl_name) when is_atom(name), do: Module.concat(name, impl_name) defp decompose_spec(returned \\ %Spec{}, body) defp decompose_spec(returned, {:__block__, _, body}), do: decompose_spec(returned, body) defp decompose_spec(returned, []), do: returned defp decompose_spec(returned, [elem | rest]), do: decompose_spec(decompose_spec_element(returned, elem), rest) defp decompose_spec(returned, body), do: decompose_spec(returned, [body]) defp decompose_spec_element(returned, elem) # defp decompose_spec_element(returned, {:def, meta, [{name, name_meta, noargs}]}) when is_atom(noargs), do: decompose_spec_element(returned, {:def, meta, [{name, name_meta, []}]}) defp decompose_spec_element(returned, {:def, _meta, [head]} = elem) do {name, args_length} = decompose_spec_head(head) callbacks = [{name, args_length, elem} | returned.callbacks] %{returned | callbacks: callbacks} end defp decompose_spec_element(returned, {:def, meta, [head, _body]}=elem) do {name, args_length} = decompose_spec_head(head) head = {:def, meta, [head]} callbacks = [{name, args_length, head, elem} | returned.callbacks] %{returned | callbacks: callbacks} end defp decompose_spec_element(returned, {:deftest, meta, [{name, _, scope}, checks]}) when is_atom(scope) do callbacks = [{:extra, :test, name, meta, checks} | returned.callbacks] %{returned | callbacks: callbacks} end defp decompose_spec_element(_returned, unhandled_elem), do: raise %InvalidProtocolSpecification{ast: unhandled_elem} defp decompose_spec_head(head) defp decompose_spec_head({:when, _when_meta, [head, _guard]}) do decompose_spec_head(head) end defp decompose_spec_head({name, _name_meta, args} = head) when is_atom(name) and is_list(args) do if([] === args, do: raise %InvalidProtocolSpecification{ast: head}) {name, length(args)} end defp decompose_spec_head(head), do: raise %InvalidProtocolSpecification{ast: head} defp verify_valid_spec(spec) do callbacks = Enum.uniq_by(spec.callbacks, fn {name, arity, _elem} -> {name, arity} {name, arity, _elem_head, _elem} -> {name, arity} {:extra, :test, name, _meta, _checks} -> {:extra, :test, name} end) if length(spec.callbacks) !== length(callbacks) do [{name, arity, _elem}|_] = spec.callbacks -- callbacks raise %DuplicateSpecification{name: name, arity: arity} end callbacks = Enum.sort(spec.callbacks) %{spec | callbacks: callbacks} end defp verify_valid_spec_on_module(proto, spec, module) do spec.callbacks |> Enum.map(fn {name, arity, _} -> if :erlang.function_exported(module, name, arity) do :ok else raise %MissingRequiredProtocolDefinition{proto: proto, impl: module, name: name, arity: arity} end {_name, _arity, _, _} -> :ok {:extra, :test, _name, _meta, _checks} -> :ok end) :ok end defp gen_impl_test_asts(spec) do opts = Macro.var(:opts, nil) Enum.filter(spec.callbacks, fn {:extra, :test, _name, _meta, _checks} -> true _ -> false end) |> case do [] -> [] tests -> Enum.map(tests, fn {:extra, :test, name, _meta, body} -> quote do def __tests_pex__(unquote(name), unquote(opts)) do _ = unquote(opts) unquote_splicing(List.wrap(body[:do])) end end end) end end def load_tests_from_impls(callbacks) do opts = Macro.var(:opts, __MODULE__) tests = Enum.flat_map(callbacks, fn {:extra, :test, name, _meta, _checks} -> [quote do __tests_pex__(unquote(name), unquote(opts)) end] _ -> [] end) [quote do def __tests_pex__(unquote(opts)) do unquote_splicing(tests) end end] end defp load_test_from_impls(proto, {:extra, :test, name, meta, _checks}, impls) do opts = Macro.var(:opts, __MODULE__) [quote do def __tests_pex__(unquote(name), unquote(opts)) do unquote_splicing(Enum.map(impls, fn impl -> quote do try do unquote(impl).__tests_pex__(unquote(name), unquote(opts)) rescue exc -> exc catch err -> err end |> case do {:ok, _value} -> :ok {:error, err_data} -> raise %ProtocolExTestFailure{ proto: unquote(proto), type: unquote(impl), name: unquote(name), meta: unquote(meta), value: err_data, } :ok -> :ok true -> :ok nil -> :ok err_data -> raise %ProtocolExTestFailure{ proto: unquote(proto), type: unquote(impl), name: unquote(name), meta: unquote(meta), value: err_data, } end end end)) end end] end defp load_test_from_impls(_proto, _abstract, _impls), do: [] defp load_abstract_from_impls(pname, abstract, impls, returning \\ []) defp load_abstract_from_impls(pname, abstract, [], returning) do case abstract do {name, arity, ast_head} -> args = get_args_from_head(ast_head) # if([] == args, do: raise %InvalidProtocolSpecification{message: "#{name} has no arguments, need at least 1 to type dispatch"}) # first_arg = if([] == args, do: :type_only, else: hd(args)) # rest_args = if([] == args, do: [], else: tl(args)) # rest_args = # Enum.map(rest_args, fn arg -> # quote do # _ = unquote(arg) # end # end) body = {:raise, [context: Elixir, import: Kernel], [{:%, [], [{:__aliases__, [alias: false], [:ProtocolEx, :UnimplementedProtocolEx]}, {:%{}, [], # [proto: pname, name: name, arity: arity, value: first_arg] [proto: pname, name: name, arity: arity, value: args] }]}]} body = quote do # unquote_splicing(rest_args) unquote(body) end catch_all = append_body_to_head(ast_head, body) :lists.reverse(returning, [catch_all]) {_name, _arity, _ast_head, ast_fallback} -> :lists.reverse(returning, [ast_fallback]) {:extra, :test, _name, _meta, _checks} -> [] # impls = returning # [quote do # def __tests_pex__(unquote(name)) do # unquote_splicing(Enum.map(impls, fn {impl} -> # quote(do: unquote(impl).__tests_pex__(unquote(name))) # end)) # end # end] end end defp load_abstract_from_impls(pname, abstract, [impl | impls], returning) do case abstract do {name, arity, ast_head} -> if Enum.any?(impl.module_info()[:exports], fn {^name, ^arity} -> true; _ -> false end) do {name, ast_head} else raise %MissingRequiredProtocolDefinition{proto: pname, impl: impl, name: name, arity: arity} end {name, arity, ast_head, _ast_fallback} -> if Enum.any?(impl.module_info()[:exports], fn {^name, ^arity} -> true; _ -> false end) do {name, ast_head} else :skip end {:extra, :test, _name, _meta, _checks} = test -> test end |> case do :skip -> load_abstract_from_impls(pname, abstract, impls, returning) {name, ast_head} -> matchers = List.wrap(impl.__matcher__()) args = get_args_from_head(ast_head) arity = length(args) if :erlang.function_exported(impl, :__inlined__, 1) do impl.__inlined__({name, arity}) else nil end |> case do nil -> body = build_body_call_with_args(impl, name, args) head_args = bind_matcher_to_args(matchers, args) ast_head = replace_head_args_with(ast_head, head_args) guard = get_guards_from_matchers(matchers) ast_head = if(guard == true, do: ast_head, else: add_guard_to_head(ast_head, guard)) ast = append_body_to_head(ast_head, body) load_abstract_from_impls(pname, abstract, impls, [ast | returning]) inlined_impls when is_list(inlined_impls) -> guard = get_guards_from_matchers(matchers) inlined_impls = if(guard == true) do # TODO: Maybe change this to force guard on inlined heads? Probably not, maybe only on plain variables? inlined_impls = Enum.map(inlined_impls, &add_guard_to_head(&1, guard)) load_abstract_from_impls(pname, abstract, impls, inlined_impls ++ returning) else load_abstract_from_impls(pname, abstract, impls, inlined_impls ++ returning) end end {:extra, :test, _name, _meta, _checks} -> returning = [{impl} | returning] load_abstract_from_impls(pname, abstract, impls, returning) end end defp get_args_from_head(ast_head) defp get_args_from_head({:def, _meta, [{:when, _when_meta, [{_name, _name_meta, args}, _guard]}]}) do # Enum.map(List.wrap(args), fn # {name, _, scope} = ast when is_atom(name) and is_atom(scope) -> ast # end) args end defp get_args_from_head({:def, _meta, [{_name, _name_meta, args}]}) do # Enum.map(List.wrap(args), fn # {name, _, scope} = ast when is_atom(name) and is_atom(scope) -> ast # end) args end defp bind_matcher_to_args(matcher, args, returned \\ []) defp bind_matcher_to_args(_matcher, [], returned), do: :lists.reverse(returned) defp bind_matcher_to_args([], args, returned), do: :lists.reverse(returned, args) defp bind_matcher_to_args([{:when, _when_meta, [binding_ast, _when_call]} | matchers], [arg_ast | args], returned) do arg = {:=, [], [binding_ast, arg_ast]} bind_matcher_to_args(matchers, args, [arg | returned]) end defp bind_matcher_to_args([binding_ast | matchers], [arg_ast | args], returned) do arg = {:=, [], [binding_ast, arg_ast]} bind_matcher_to_args(matchers, args, [arg | returned]) end defp replace_head_args_with(ast_head, head_args) defp replace_head_args_with({:def, meta, [{:when, when_meta, [{name, name_meta, _args}, guards]} | rest]}, head_args) do {:def, meta, [{:when, when_meta, [{name, name_meta, head_args}, guards]} | rest]} end defp replace_head_args_with({:def, meta, [{name, name_meta, _args} | rest]}, head_args) do {:def, meta, [{name, name_meta, head_args} | rest]} end defp get_guards_from_matchers(matchers, returned \\ []) defp get_guards_from_matchers([], []), do: true defp get_guards_from_matchers([], returned), do: Enum.reduce(:lists.reverse(returned), fn(ast, acc) -> {:and, [], [ast, acc]} end) defp get_guards_from_matchers([{:when, _when_meta, [_bindings, guard]} | matchers], returned) do get_guards_from_matchers(matchers, [guard | returned]) end defp get_guards_from_matchers([_when_ast | matchers], returned) do get_guards_from_matchers(matchers, returned) end defp add_guard_to_head(ast_head, guard) defp add_guard_to_head({:def, meta, [{:when, when_meta, [head, old_guard]} | rest]}, guard) do {:def, meta, [ {:when, when_meta, [head, {:and, [], [old_guard, guard]}]} | rest ]} end defp add_guard_to_head({:def, meta, [head | rest]}, guard) do {:def, meta, [ {:when, [], [head, guard]} | rest ]} end defp build_body_call_with_args(module, name, args) do quote do unquote(module).unquote(name)(unquote_splicing(args)) end end defp append_body_to_head(ast_head, body) defp append_body_to_head({:def, meta, args}, body) do {:def, meta, args++[[do: body]]} end end if Mix.env() === :test do # MyDecimal for Numbers defimpl Numbers.Protocols.Addition, for: Tuple do def add({MyDecimal, _s0, :sNaN, _e0}, {MyDecimal, _s1, _c1, _e1}), do: throw :error def add({MyDecimal, _s0, _c0, _e0}, {MyDecimal, _s1, :sNaN, _e1}), do: throw :error def add({MyDecimal, _s0, :qNaN, _e0} = d0, {MyDecimal, _s1, _c1, _e1}), do: d0 def add({MyDecimal, _s0, _c0, _e0}, {MyDecimal, _s1, :qNaN, _e1} = d1), do: d1 def add({MyDecimal, s0, :inf, e0} = d0, {MyDecimal, s0, :inf, e1} = d1), do: if(e0 > e1, do: d0, else: d1) def add({MyDecimal, _s0, :inf, _e0}, {MyDecimal, _s1, :inf, _e1}), do: throw :error def add({MyDecimal, _s0, :inf, _e0} = d0, {MyDecimal, _s1, _c1, _e1}), do: d0 def add({MyDecimal, _s0, _c0, _e0}, {MyDecimal, _s1, :inf, _e1} = d1), do: d1 def add({MyDecimal, s0, c0, e0}, {MyDecimal, s1, c1, e1}) do {c0, c1} = cond do e0 === e1 -> {c0, c1} e0 > e1 -> {c0 * pow10(e0 - e1), c1} true -> {c0, c1 * pow10(e1 - e0)} end c = s0 * c0 + s1 * c1 e = Kernel.min(e0, e1) s = cond do c > 0 -> 1 c < 0 -> -1 s0 == -1 and s1 == -1 -> -1 # s0 != s1 and get_context().rounding == :floor -> -1 true -> 1 end {s, Kernel.abs(c), e} end def mult({MyDecimal, s0, c0, e0}, {MyDecimal, s1, c1, e1}) do s = s0 * s1 {s, c0 * c1, e0 + e1} end def add_id(_num), do: {MyDecimal, 1, 0, 0} _pow10_max = Enum.reduce 0..104, 1, fn int, acc -> def pow10(unquote(int)), do: unquote(acc) def base10?(unquote(acc)), do: true acc * 10 end def pow10(num) when num > 104, do: pow10(104) * pow10(num - 104) end defimpl Numbers.Protocols.Multiplication, for: Tuple do def mult({MyDecimal, _s0, :sNaN, _e0}, {MyDecimal, _s1, _c1, _e1}), do: throw :error def mult({MyDecimal, _s0, _c0, _e0}, {MyDecimal, _s1, :sNaN, _e1}), do: throw :error def mult({MyDecimal, _s0, :qNaN, _e0}, {MyDecimal, _s1, _c1, _e1}), do: throw :error def mult({MyDecimal, _s0, _c0, _e0}, {MyDecimal, _s1, :qNaN, _e1}), do: throw :error def mult({MyDecimal, _s0, 0, _e0}, {MyDecimal, _s1, :inf, _e1}), do: throw :error def mult({MyDecimal, _s0, :inf, _e0}, {MyDecimal, _s1, 0, _e1}), do: throw :error def mult({MyDecimal, s0, :inf, e0}, {MyDecimal, s1, _, e1}) do s = s0 * s1 {s, :inf, e0+e1} end def mult({MyDecimal, s0, _, e0}, {MyDecimal, s1, :inf, e1}) do s = s0 * s1 {s, :inf, e0+e1} end def mult({MyDecimal, s0, c0, e0}, {MyDecimal, s1, c1, e1}) do s = s0 * s1 {s, c0 * c1, e0 + e1} end def mult_id(_num), do: {MyDecimal, 1, 1, 0} end end