When designing Funx I leveraged protocols, which meant some concessions.

In Haskell, you can state class relationships like “every Monad is a Functor” (via superclass constraints). In Elixir, there isn’t an equivalent way to express that kind of protocol hierarchy. So instead of creating separate protocols and implying a relationship I can’t encode, I grouped the operations into a single protocol. I could split out Functor and Applicative, but in practice you are just importing functions and it’s simpler to import Monad and get map, ap, and bind.

Also, in Elixir, a protocol module can’t serve as the public entry point for functions. That’s not a problem for monads: their contexts provide namespaces: Either, Maybe, Reader. But with Eq and Ord, there aren’t obvious namespaces, so I had a choice:

  1. Name the protocol Eq and put the utility functions in Eq.Utils.
  2. Name the protocol something like Eq.Protocol and use Eq as the namespace for the grab-bag of equality utilities.

I chose consistency with other protocols like Monad, Foldable, and Filterable. Having Utils in both the Ord and Eq namespaces was annoying, but manageable by aliasing them, for example as EqUtil.

Once I started building a DSL, that decision became harder to justify. The DSL couldn’t live in the Eq protocol module, which meant either explaining that the DSL lived under Eq.Utils, or introducing yet another namespace, Eq.Dsl.

At that point it was clear that in Funx, the Eq and Ord protocols mostly live in the background, which means choosing protocol naming consistency makes less sense. So I’m switching to option 2: Eq and Ord are the primary public entry points for the utility functions and the DSL, and the protocols move to Eq.Protocol and Ord.Protocol.

I considered waiting until 1.0, but once I made the decision it didn’t make sense to delay. The longer I waited, the more code I (and others) would generate that would need to be migrated.

ChangeLog

Eq module changes

  • Funx.Eq (protocol) → Funx.Eq.Protocol

    • The equality protocol is now Funx.Eq.Protocol
    • Protocol implementations must use defimpl Funx.Eq.Protocol, for: YourType
  • Funx.Eq.UtilsFunx.Eq

    • Utility functions moved from Funx.Eq.Utils to Funx.Eq
    • DSL merged into Funx.Eq (no more separate Funx.Eq.Dsl)
    • use Funx.Eq imports only the eq DSL macro
    • alias Funx.Eq for utility functions (optional, or use fully qualified)

Ord module changes

  • Funx.Ord (protocol) → Funx.Ord.Protocol

    • The ordering protocol is now Funx.Ord.Protocol
    • Protocol implementations must use defimpl Funx.Ord.Protocol, for: YourType
  • Funx.Ord.UtilsFunx.Ord

    • Utility functions moved from Funx.Ord.Utils to Funx.Ord
    • DSL merged into Funx.Ord (no more separate Funx.Ord.Dsl)
    • use Funx.Ord imports only the ord DSL macro
    • alias Funx.Ord for utility functions (optional, or use fully qualified)

Migration guide

Eq changes:

# Before
alias Funx.Eq.Utils
use Funx.Eq.Dsl          # DSL macros

Utils.contramap(&(&1.age))

defimpl Funx.Eq, for: MyStruct do
  def eq?(a, b), do: a.id == b.id
end

# After
use Funx.Eq              # Imports eq DSL macro
alias Funx.Eq            # For utility functions

Eq.contramap(&(&1.age))

defimpl Funx.Eq.Protocol, for: MyStruct do
  def eq?(a, b), do: a.id == b.id
end

Ord changes:

# Before
alias Funx.Ord.Utils
use Funx.Ord.Dsl         # DSL macros

Utils.contramap(&(&1.score))

defimpl Funx.Ord, for: MyStruct do
  def lt?(a, b), do: a.score < b.score
end

# After
use Funx.Ord             # Imports ord DSL macro
alias Funx.Ord           # For utility functions

Ord.contramap(&(&1.score))

defimpl Funx.Ord.Protocol, for: MyStruct do
  def lt?(a, b), do: a.score < b.score
end

Default parameter changes:

  • Functions with ord \\ Ord now use ord \\ Funx.Ord.Protocol
  • DSL parser defaults to Funx.Ord.Protocol for comparison checks

Rationale

This reorganization provides:

  • Clear separation: Protocols (*.Protocol) vs utilities (Funx.Eq, Funx.Ord)
  • Minimal imports: use imports only the DSL macro, not all functions
  • Better discoverability: Main modules contain the utilities users interact with
  • User control: Users decide whether to alias or use fully qualified names