This is where your mental model breaks down: there is only and there can only be one update/3
function. That function may be implemented in terms of multiple function clauses - but they all belong to the same function.
The Erlang style of specs make that much clearer:
-spec foo(pos_integer()) -> pos_integer()
; (integer()) -> integer().
So strictly speaking the return type of SystemRegistry.update/3
is the sum type Transaction.t() | {:ok, {new :: map, old :: map}} | {:error, term}
.
A more type-aware module API would have
SystemRegistry.update_with_scope/3
SystemRegistry.update_with_transaction/3
- And a convenience function
SystemRegistry.update/3
that delegates appropriately.
but given that Erlang/Elixir is dynamically typed you will run into functions that have only been typed after the fact rather than being designed with types in mind.