Another deleted scene from my book

There are many ways to solve equality: duck typing, injection, protocol dispatch.

Here’s why Funx uses the one it does:

funx

2 Likes

Hey @JKWA,

First of all, thanks for your book.

I bought it recently and it’s been an enjoyable journey reading it.
I read your article and ran the LiveBook, and I liked your idea so much that I decided to create my own version of your implementation.
The main difference is that I created a separate module for the Protocol and used defdelegate to route the function depending on the arity.

I would like to your opinion about it.

Thanks!

defmodule Book do 
  defstruct [:id, :title, :author]
end

defmodule DVD do
  defstruct [:id, :title, :director]
end
defprotocol Eq.Protocol do
  def eq?(a, b)
end
defmodule Eq do
  defdelegate eq?(a, b), to: Eq.Protocol

  def eq?(a, b, comparator) when is_function(comparator, 2) do
    comparator.(a, b)
  end
end
defimpl Eq.Protocol, for: Book do
  def eq?(%Book{id: id1}, %Book{id: id2}), do: id1 == id2
  def eq?(_, _), do: false
end

defimpl Eq.Protocol, for: DVD do
  def eq?(%DVD{id: id1}, %DVD{id: id2}), do: id1 == id2
  def eq?(_, _), do: false
end
hobbit_book1 = %Book{id: 1, title: "Hobbit", author: "Tolkien"}
hobbit_book2 = %Book{id: 1, title: "Hobbits", author: "Tolkien"}
title_comparator = fn a, b -> 
  a.title == b.title
end

Eq.eq?(hobbit_book1, hobbit_book2, title_comparator)

Sure, here’s my take.

From a functional point of view, I think of the protocol as the abstraction itself, defining what it means to be equal. I use Eq.Utils as a convenient place to keep helper functions that work with Eq.

I had considered naming Eq as Eq.Protocol, but in the end I leaned away from that. I felt that framing treated the protocol more like an interface for modules to implement, which doesn’t quite match how I think about the abstraction.

The combination of defdelegate eq?(a, b), to: Eq.Protocol and eq?(a, b, comparator) is behaviorally equivalent to my approach, but to me it reads a bit like OOP. Nothing wrong with that, just a different frame.

But those are really just semantics, the real difference is in how we handle the third argument. You’re passing a function, while I’m passing a map with an :eq? key that holds the function. Treating the comparison logic as data makes it easier to compose later. For instance, check out the Funx monoid.

1 Like

You might find AI Tutor for Functional Programming - Funx , interesting. Feed in your example and get Claude to focus on the different eq?/3 functions. If it is attending to the usage rules, it does a pretty good job of explaining why you might choose one over the other.

1 Like