As I mentioned in my original post for my fractal series, I was unsatisfied with the way that I was abstracting common code. While cleaning up some params this week, I also figured out an abstraction for my escape-time algorithm that’s not too bad.
My code this week is in my mandelbrot repo on Github tagged blog_2016_07_25.  Links to the previous articles are available in the README.
Let’s look at Fractals.EscapeTime.Mandelbrot from a few weeks ago.
Mandelbrot defined these three module attributes:
@magnitude_cutoff         2.0
@magnitude_cutoff_squared 4.0
@max_iterations           256
I talked about the escape-time algorithm a few weeks back. The algorithm needs two numbers: a magnitude cutoff and a maximum number of iterations. Exceeding the cutoff means the original grid point is outside the fractal; exceeding the maximum number of iterations means the grid point is inside. Triggering either one means stopping the escape-time algorithm.
I defined this function, but I should have called it outside?:
def escaped?(z) do
  Complex.magnitude_squared(z) >= @magnitude_cutoff_squared
end
I used @magnitude_cutoff_squared because computing the square root is very slow (as far as mathematical computations go).  Just compare the square of the magnitude to the square of the cutoff.
Consequently, @magnitude_cutoff was unused.
I did not define an inside? function but should have.  Instead I inlined its definition in the termination step in the escape-time algorithm:
Stream.drop_while(fn {z, i} -> !escaped?(z) && i < @max_iterations end)
I hate that I extracted only the one function here.1
So when I wrote a few paragraphs back that “I did not define an inside? function”, that’s technically true.  I did define a macro for Fractals.Colorizer, but called it escaped?:
defmacro escaped?(iterations) do
  quote do
    unquote(iterations) >= @maximum_iterations
  end
end
It would have been better if I had used this macro in the escape-time algorithms, too. It would have been much much better if I had named it right, instead of naming it pretty much the opposite of what it computed. It’s a code smell so bad, they don’t even have a name for it. At least it did compute the right thing for the colorizing algorithms.
The the escape-time algorithm is necessarily a bit long:
def escape_time(grid_point) do
  cmplx(0.0, 0.0)
  |> Stream.iterate(&iterator(&1,grid_point))
  |> Stream.with_index
  |> Stream.drop_while(fn {z, i} -> !escaped?(z) && i < @max_iterations end)
  |> Stream.take(1)
  |> Enum.to_list
  |> List.first
end
The drop_while test could be simplified, but overall this isn’t bad code.  It’s bad because I had three different versions of this in the three fractals modules with very little variation.
I knew I would eventually clean all of this up, and as I expected, the trigger to start the clean up came when I started looking at new fractals.  I tried out a sinusoidal Mandelbrot a few weeks ago and found that I needed more iterations and a larger cutoff.  I could have set the module attributes on the SinusoidalMandelbrot module to higher values, but then it occurred to me that I might like to play around with those settings, even on my existing fractals.
So I set out to turn the magnitude cutoff (squared) and maximum iterations into Params.  And all of the smells described above got fixed along the way.
As I mentioned above, the escaped? function was poorly named and duplicated in each module for a fractal.  escaped? the macro was named completely wrong and as a macro in Fractals.Colorizer.  inside? was inlined in the escape-time algorithm.
So I created Fractals.EscapeTime.Helpers.
inside? is the escaped? macro moved out of Fractals.Colorizer:
defmacro inside?(iterations, max_iterations) do
  quote do
    unquote(iterations) >= unquote(max_iterations)
  end
end
outside? is now a macro:
defmacro outside?(z, cutoff_squared) do
  quote do
    Complex.magnitude_squared(unquote(z)) >= unquote(cutoff_squared)
  end
end
max_iterations and cutoff_squared are now parameters because they come from a Params struct, not from module attributes.  I don’t pass in the Params struct itself because I want to use these macros in when guards on functions (or at least inside?), and unpacking a struct isn’t allowed in a guard.
For the escape-time algorithm, I have a done? function:
def done?({z, iterations}, params) do
  outside?(z, params.cutoff_squared) ||
    inside?(iterations, params.max_iterations)
end
Since I don’t use done? in a guard, I can implement it as a function that takes a Params struct.  z and iterations come in a tuple because that’s their natural form in the escape-time algorithm.
With the new done? function, I could write this:
def pixels(grid_points, params) do
  Enum.map(grid_points, &escape_time(&1, params))
end
def escape_time(grid_point, params) do
  Complex.zero
  |> Stream.iterate(&iterator(&1, grid_point))
  |> Stream.with_index
  |> Stream.drop_while(fn zi -> !done?(zi, params) end)
  |> Stream.take(1)
  |> Enum.to_list
  |> List.first
end
In last year’s Elixir solution, I passed in a function for iterator which was determined by a case expression.2  This time I figured I’d use the __using__ macro to provide common code in Fractals.EscapeTime.
pixels is the same across all three fractals, so that was an obvious candidate to pull into __using__:
defmacro __using__(_options) do
  quote do
    def pixels(grid_points, params) do
      Enum.map(grid_points, &escape_time(&1, params))
    end
  end
end
When I use Fractals.EscapeTime in Mandelbrot and Julia and BurningShip, I get this definition of pixels for free in each module, as if I typed it into each of those modules.  escape_time is a free variable in the macro definition, but once used it’s bound to the definition in the module that using Fractals.EscapeTime.
The escape_time functions looks pretty similar in the three modules.  Mandelbrot and BurningShip3:
def escape_time(grid_point, params) do
  Complex.zero
  |> Stream.iterate(&iterator(&1,grid_point))
  |> Stream.with_index
  |> Stream.drop_while(fn zi -> !done?(zi, params) end)
  |> Stream.take(1)
  |> Enum.to_list
  |> List.first
end
Julia:
def escape_time(grid_point, c) do
  grid_point
  |> Stream.iterate(&iterator(&1,c))
  |> Stream.with_index
  |> Stream.drop_while(fn zi -> !done?(zi, params) end)
  |> Stream.take(1)
  |> Enum.to_list
  |> List.first
end
It’s only the first two lines of the pipe that are different: what value do start with, and what do you pass to iterator?  I got hung up on trying to abstract out the Stream.iterate(&iterator(...args...) since the only difference is what gets passed to iterator.  Every solution just seemed more complicated than it needed to be, and, really, what’s wrong with a little bit of duplication?
So I looked for function calls that I could pull out easily: everything from Stream.with_index to the end.  Another function was born, EscapeTime.escape_time:
def escape_time(stream, params) do
  stream
  |> Stream.with_index
  |> Stream.drop_while(fn zi -> !done?(zi, params) end)
  |> Stream.take(1)
  |> Enum.to_list
  |> List.first
end
I could rewrite Mandelbrot.escape_time (and BurningShip.escape_time) like so:
def escape_time(grid_point, params) do
  Complex.zero
  |> Stream.iterate(&iterator(&1, grid_point))
  |> EscapeTime.escape_time
end
Julia.escape_time became:
def escape_time(grid_point, params) do
  grid_point
  |> Stream.iterate(&iterator(&1, params.c))
  |> EscapeTime.escape_time
end
As I looked at this, I realized that EscapeTime.escape_time could be called by pixels.
So Mandelbrot.escape_time became Mandelbrot.iterate:
def iterate(grid_point, _params) do
  Stream.iterate(Complex.zero, &iterator(&1, grid_point))
end
The other two modules for fractals got the same refactor.
I updated pixels in the EscapeTime.__using__ macro:
defmacro __using__(_options) do
  quote do
    def pixels(grid_points, params) do
      Enum.map(grid_points, fn grid_point ->
        grid_point
        |> iterate(params)
        |> Fractals.EscapeTime.escape_time(params)
      end)
    end
  end
end
iterate is bound to the module’s own version.  Everything else is a parameter or an explicit call to a very specific function.
I really like this solution.
I’m going to play around with the fractals this week, tweaking the cutoff and maximum iterations. I may implement another coloring scheme or two. And if I’m really lucky, I’ll try a new fractal or two.
Someday I’ll have to blog about parallel structure (nothing to do with parallel computing).  Structural parallelism would suggest that I extract inside? so that the two clauses are at the same level. ↩
I’m not going to show the code here because it’s a function which generates a function that generates a function. I kid you not. ↩
BurningShip.escape_time from a few weeks ago actually looks identical to Julia.escape_time, but if you look at BurningShip.pixels you’ll see that I pass in Complex.zero for grid_point and grid_point for c.  The right values are getting in, I just named them completely wrong.  If I passed in the right values, escape_time ends up looking identical to the Mandelbrot version.  I think this mistake is even more embarrassing than naming the inside? macros as escaped?.  That’s why I hid my confession in a footnote. ↩