Inheritance means knowing who your parent is.
def point() do [ __own_symbol__: :point, __superclass_symbol__: :anything, __instance_methods__: [ class: fn (_this) -> point end, add_instance_values: fn (this, x, y) -> this |> merge([x: x, y: y]) end, shift: fn (this, xinc, yinc) -> make(point, [get(this, :x) + xinc, get(this, :y) + yinc]) end ] ] end
Now to define the anything class, referenced above by the keyword :__superclass_symbol__.
def anything do [ __own_symbol__: :anything, __instance_methods__: [ add_instance_values: fn(this) -> this end, class_name: &get(&1, :__class_symbol__), class: fn (this) -> apply(__MODULE__, get(this, :__class_symbol__), ) end ] ] end
And now the real magic: method lookup!
The top-level method lookup function:
def method_cache(class) do import Enum class_symbol = class |> get(:__own_symbol__) method_maps = lineage(class_symbol) |> map(&class_instance_methods/1) method_maps |> reduce(&Dict.merge(&2, &1)) end
Notice that I've had to reverse the order of the arguments to Dict.merge/2. This is because reduce passes arguments in the order next_item, accumulator, but I want methods from each successive class in the lineage to override methods in earlier, less-specialized classes.
A helper to get the instance methods defined for a class:
def class_instance_methods(class_symbol) do apply(__MODULE__, class_symbol, ) |> get(:__instance_methods__) end
Another helper to get the parent class symbol:
def class_symbol_above(class_symbol) do apply(__MODULE__, class_symbol, ) |> get(:__superclass_symbol__) end
…and a function to get the ancestry chain of a given class:
@doc """ iex> lineage(:point) [:anything, :point] """ def lineage(nil), do:  def lineage(class_symbol) do [class_symbol|class_symbol |> class_symbol_above |> lineage] |> Enum.reverse end
Now to update apply_message_to to use this new code:
defp apply_message_to(class, object, message, args) do class |> method_cache |> get(message) |> apply([object|args]) end
Before I go any further, I feel a compulsion to try and golf down the method_cache function.
def method_cache(class) do import Enum class |> get(:__own_symbol__) |> lineage |> map(&class_instance_methods/1) |> reduce(&Dict.merge(&2, &1)) end
Exercise 1: factorial
Just for fun, I'll do this one in a single definition instead of multiple pattern-matching definitions.
@doc """ iex> factorial(5) 120 """ def factorial(n) do case n do 0 -> 1 1 -> 1 _ -> n*factorial(n-1) end end
Exercise 2: factorial, accumulator style
@doc """ iex> factorial_acc(5) 120 iex> factorial_acc(0) 1 """ def factorial_acc(n, acc // 1) do if n == 0 || n == 1 do acc else factorial_acc(n-1, n*acc) end end
Exercises 3-6: implementing reduce
I'm going to skip these for now.
One of the things I like about this section, and this book, is that Marick explicitly identifies functional patterns, such as the two styles of recursive method definition. He even shows “ideal forms” of the two patterns, with placeholders for the parts that change.
I've definitely seen these styles in use, but hadn't seen them clearly elucidated as patterns before. If nothing else, this book should put the rest the notion, occasionally advanced, that patterns are somehow not applicable to functional programming. Every community develops patterns; being able to identify them and point out which parts are consistent and which parts change with the application is one of the marks of a good software writer.