Defining functions at top level without using macros

I’d like to be able to do the following:

defmodule MyModule do
  import Defhelper

  data1 = ...
  data2 = ...

  defhelper(:name, data1, data2)
end

I’d like defhelper (whatever that is) to receive the values – not the AST – of data1 and data2 and use that to define a bunch of functions. I’d like to reuse data1 and data2 in other places without spelling it all out explicitly. I’m having the following problem:

  1. If defhelper is a macro, it receives AST and not actual values, which makes it impossible to feed it variable names instead of values

  2. If defhelper is a function that returns a quoted expression, then it won’t be “incorporated” in the module and no functions will be defined.

Is there a way of using the values of data1 and data2 to generate a quoted expression that will be inserted in the module as function definitions?

Maybe I should explain in a more concrete way what I’d like to do and why I’d like to do it. I’m working on a mutation testing library called Darwin (forum thread here: Mutation testing - Mutating BEAM bytecode). Despite the title, I’m not mutating the BEAM bytecode. Instead, I will extract the erlang abstract code (that’s the same as the Erlang AST) from the .beam files and mutate that.

To avoid recompiling the modules to many times, I have a way of adding hundreds of mutations to an Erlang module in such a way that I can toggle the mutations on or off. That way, by flipping a switch (probably an entry in an ETS table, or something like that), I can decide which mutation I want to activate without recompiling the code. I expect that will save a lit of time over something like exavier, which requires either (1) recompiling the code for each mutation or (2) insert lots of mutations at the same time, thus losing fine grianed control on what we’re testing.

As I said, my approach requires replacing instances of

A + B.
% in abstract code:
{:op, 1, :+, {:var, 1, :A}, {:var, 1, :B}}

and replacing that with something like

'Elixir.Darwing.Mutator':do_op_add_mutator(A, B).
% in abstract code:
{:call, 1,
 {:remote, 1, {:atom, 1, Darwin.Mutator}, {:atom, 1, :do_op_add_mutator}},
 [{:var, 1, :A}, {:var, 1, :B}]}

Where the 'Elixir.Darwing.Mutator':do_op_add_mutator/2 function contains the “machinery” to either execute the original code (A + B.) or one of the possible mutations (e.g. A - B., A * B, A, B, etc.). Writing the function in full is a little tedious and error prone, and it must be repeated for a bunch of operators (the arithmetic operators, comparison operators, etc). I’ve decided to simplify things by writing some suitable macros. Currently, I have this for the 4 arithmetic operators:

defmodule Darwin.Mutators do
  # don't worry about the macro in this module; it's a pretty simple one
  import Darwin.Mutator.Helpers.DefHelper, only: [def_bin_op_mutator: 4]

  def_bin_op_mutator(:op_add_mutator, :+, &Kernel.+/2, [
    [f: fn a, _b -> a end, text: "delete right operand from '+' (e.g. 'a + b' -> 'a')"],
    [f: fn _a, b -> b end, text: "delete left operand from '+' (e.g. 'a + b' -> 'b')"],
    [f: fn a, b -> a - b end, text: "replace operator '+' by '-'"],
    [f: fn a, b -> a * b end, text: "replace operator '+' by '*'"],
    [f: fn a, b -> a / b end, text: "replace operator '+' by '/'"],
    [f: fn a, b -> rem(a, b) end, text: "replace operator '+' by 'rem'"]
  ])

  def_bin_op_mutator(:op_sub_mutator, :-, &Kernel.-/2, [
    [f: fn a, _b -> a end, text: "delete right operand from '-' (e.g. 'a - b' -> 'a')"],
    [f: fn _a, b -> b end, text: "delete left operand from '-' (e.g. 'a - b' -> 'b')"],
    [f: fn a, b -> b - a end, text: "switch order of arguments to '-' (e.g. 'a - b' -> 'b - a')"],
    [f: fn a, b -> a + b end, text: "replace operator '-' by '+'"],
    [f: fn a, b -> a * b end, text: "replace operator '-' by '*'"],
    [f: fn a, b -> a / b end, text: "replace operator '-' by '/'"],
    [f: fn a, b -> rem(a, b) end, text: "replace operator '-' by 'rem'"]
  ])

  def_bin_op_mutator(:op_mul_mutator, :*, &Kernel.+/2, [
    [f: fn a, _b -> a end, text: "delete right operand from '*' (e.g. 'a * b' -> 'a')"],
    [f: fn _a, b -> b end, text: "delete left operand from '*' (e.g. 'a * b' -> 'b')"],
    [f: fn a, b -> a + b end, text: "replace operator '*' by '+'"],
    [f: fn a, b -> a - b end, text: "replace operator '*' by '-'"],
    [f: fn a, b -> a / b end, text: "replace operator '*' by '/'"],
    [f: fn a, b -> rem(a, b) end, text: "replace operator '*' by 'rem'"]
  ])

  def_bin_op_mutator(:op_div_mutator, :/, &Kernel.//2, [
    [f: fn a, _b -> a end, text: "delete right operand from '/' (e.g. 'a / b' -> 'a')"],
    [f: fn _a, b -> b end, text: "delete left operand from '/' (e.g. 'a / b' -> 'b')"],
    [f: fn a, b -> b / a end, text: "switch order of arguments to '/' (e.g. 'a / b' -> 'b / a')"],
    [f: fn a, b -> a + b end, text: "replace operator '/' by '+'"],
    [f: fn a, b -> a - b end, text: "replace operator '/' by '-'"],
    [f: fn a, b -> a * b end, text: "replace operator '/' by '*'"],
    [f: fn a, b -> rem(a, b) end, text: "replace operator '/' by 'rem'"]
  ])
end

The code above defines four functions, one for each arithmetic operator. I was forced to add these large keyword lists inside the calls to the def_bin_op_mutator/4 macro (and use keyword lists instead of something else) so that the macros can receive some nice AST to work with.

But if you notice, the lists have too many repetition, and could be generated programmatically from a much smaller list, marking it easier to refactor and leaving less room for typos. That’s why I’d like to be able to manipulate the keyword lists before feeding them into the macros…

Just to be clear: I’m not asking for help implementing the Erlang abstract code mutations. The question is the one in the post above, this is just to provide some additional context.

There are two legitimate questions at this point:

  1. Am going too deep into macro wizardry just to avoid writing a couple of function bodies? Maybe, but the truth is that I trust my macros to generate said function bodies much more than I trust myself to write them manually

  2. Since I’m mutating Erlang abstract code, nothing here is elixir-specific. Should the whole library should be written in Erlang to start with, so that it can be used with Erlang projects? The answer to this is probably yes, but Elixir is a much nicer language for a prototype (since I can use macros to hide literal screens full of boring case statements and copy-pasted code). I’ll think about rewriting the whole thing in Erlang if it ever becomes stable.

1 Like

Instead of data1 = ... and so forth, why not use module attributes, they are basically values that macro’s can access?

You’re right, that solves most of the problems. I ended up doing something else, though. Because writing docstrings to the modules was quite tedious, I’ve decided to generate the mutator modules in a completely programatic way (including the docstrings). Something like this:

lias Darwin.Mutator.OpMutatorCreator

arithmetic_operator_mutators = [
  %{
    module: Darwin.Mutators.Default.OpAddMutator,
    operator: :+,
    default: quote(do: &Kernel.+/2)
  },
  # ...
]

comparison_operator_mutators = [
  %{
    module: Darwin.Mutators.Default.OpLessThanMutator,
    operator: :<,
    default: quote(do: &Kernel.</2)
  },
  # ...
]

mutators = arithmetic_operator_mutators ++ comparison_operator_mutators

for mutator <- mutators do
  OpMutatorCreator.create_mutator(mutator)
end

In the code above, OpMutatorCreator.create_mutator/1 contains some logic to decide which mutations to include for each operator and generates a complete module, with docstrings automatically generated from the operator names and available mutations.

1 Like