Thank you for that thoughtful post. Stuff inlined.
> type instance Unbundled dom (a,b) = (Signal dom a, Signal dom b)
I'll rephase/sumarise these things as I go, to make sure I have it right. The concept there is that comma pairs of things can be rendered into signals and the outcome is essentially the same as a comma pair of signals. The mathematical idea in that is that SIgnal and comma pair commute as functors.
> type instance Unbundled dom (Pair a b) = (Signal dom a, Signal dom b)
Here the mathematical idea is not that Signal and Pair commute as functors, but that a signal containing Pairs is essentially the same as a comma pair of signals.
I would deduce from that, that comma pair and Pair are intrinsically isomorphic constructions, insofar as it may be deduced by an observer limited to think only in terms of signals, because of the polymorphic type arguments. [The qualification doesn't seem limiting in any way ... oh, what about signals of signals? ]
I'd feel that the instance claim being made is programmatically dangerous because it entangles two concepts, and it should be that instead this weaker claim is put forward by the programmer:
type instance Unbundled dom (Pair a b) = Pair (Signal dom a) (Signal dom b)
That should state that Pair and Signal commute as functors. That would leave any further claim as to isomorphism between Pair and comma pair out of it for now, at the further discretion of the programmer, if it were so desired.
> the resulting type of the two instances `(Signal dom a, Signal dom b)` no longer uniquely determines the arguments.
You are saying that you cannot determine if you unbundled from a signal containing comma pairs, or a signal containing Pairs. But the rejection seems OK if one cares about that kind of thing, and one does care because one wants to be able to bundle back to the right type again, and there are now two alternatives.
However, can one not disambiguate by specifying the (target) type for bundle in each application? It's not going to be the only situation like that? It looks like an inconvenience rather than a problem.
You had
> type family Unbundled (dom :: Domain) a = res | res -> dom a
and now you will instead have
> type family Unbundled (f :: Type -> Type) a = res | res -> f a
I'm not reading those "family" things well, though I understand (now/I think) from your explanation that the res -> foo stuff asserts one/ghc should be able to work out what the foo bar are that went into it when one sees an instance of Unbundled foo bar.
Checking: from the formal type of shape Signal dom (Pair a b) one could disentangle both dom and Pair a b. From Signal dom (a,b) one could disentangle both dom and (a,b).
Now you are claiming/will claim instead that from, say, a formal type of shape Signal dom x that has been proposed as a target type for unbundle, one can always figure out Signal dom and x.
> type instance Unbundled g (Vec n a) = Vec n (g a)
This will be a claim that applying unbundle to something gets one a Vec n (g a). But as things were, unbundle is supposed to apply to signals. Has unbundle now been respecified to apply now to things of form g, not Signal dom? Is it now
unbundle : : g(Vec n a) -> Vec n (g a)
Then g = Signal dom iwould be just one possibility for g, and g has generalized Signal dom.
> type instance Unbundled f (Maybe a) = f (Maybe a)
??? I am similarly to understand
unbundle :: f(Maybe a) -> f(Maybe a)
That doesn't seem to make sense as unbundle = id could always work for that. Was Maybe (f a) not f(Maybe a) was meant as the target type for unbundle? Perhaps this is a deliberate artifice ...
> those two instances overlap through the following unifications: g ~ Maybe; f ~ Vec n, and thus violate the injectivity.
Yes, but isn't that saying one can't or shouldn't declare one of those two instances, hopefully the second, since it looked crazy to me (if I read these things right).
I can appreciate an intention here to artificially create an undesired solution via unification. But if you had said in the standard context that
type instance Unbundled dom (Maybe a) = Signal dom (Maybe a) -- !!
would you not also be creating difficulties, even if not quite the same sort? The problem looks to be that the example instance is mathematically wrong-headed whatever the context, and I would be happy to have it disallowed or otherwise screeched about by a compiler, so where's the problem?
If the intention is to show definitions like
unbundle :: Bundled f g => f(g x y) -> g(f x)(f y)
bundle :: Bundled f g => g(f x)(f y) -> f(g x y)
give rise to ambiguities sometimes, that may be so and I do not necessarily have a problem with that.
But ... provided one can see from f foo what f is and what foo is, and from g foo bar what g is and what foo is and what bar is, then one cannot mistake what bundle and unbundle are supposed to do in every situation. The type algorithm simply dismantles the argument type or the target type (and/or both simultaneously, via unifications) to get all of f, g, x, y in order to construct both the domain and codomain types for either of bundle or unbundle, as required. There is no ambiguity about it.
I think!
Peter