There are many ways to solve equality: duck typing, injection, protocol dispatch.
Here’s why Funx uses the one it does:
There are many ways to solve equality: duck typing, injection, protocol dispatch.
Here’s why Funx uses the one it does:
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.
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.