13

Is it possible to create type family instances from a fundep class? For example, let's say that I have the class

class A a b | a -> b

with some instances (imported an external library) and want to create all corresponding instances for the type family

type family A' a :: *

such that A' a ~ b iff A a b, without having to manually copy and modify the instances from the external source.

How would I do that (if it is possible)?


My most promising attempt so far,

class A' a where
    type A'_b a :: *

instance forall a b. A a b => A' a where
    type A'_b a = b

gives the error message

    The RHS of an associated type declaration mentions ‘b’
      All such variables must be bound on the LHS

So, my guess is that the answer is no? :/

Hjulle
  • 2,471
  • 1
  • 22
  • 34
  • Here is a very relevant related post: http://stackoverflow.com/questions/34645745/can-i-magic-up-type-equality-from-a-functional-dependency – Hjulle May 07 '17 at 04:30

1 Answers1

4

Don't overthink it.

class C a b | a -> b
instance C T U

roughly translates into

class C' a where
    type B a
instance C' T where
    type B T = U

They're semantically somewhat different, even though they behave in a similar way. C is a two-parameter class, but its second parameter is uniquely determined by its first. C' is a one-parameter class with an associated type. Associated types are interpreted as top-level axioms for FC's coercion system whereas fundeps basically just affect unification.

It is possible to convert between the two styles, but you have to use a newtype to bind the variables in the type equation.

newtype WrappedB a = WrappedB { unwrapB :: B a }
instance C' a => C a (WrappedB a)  -- you can't use a type synonym family in an instance

newtype Wrap a b = Wrap { unWrap :: a }
instance C a b => C' (Wrap a b) where
    type B (Wrap a b) = b  -- b needs to be bound on the LHS of this equation

In general I don't advise writing the sort of generic instance you've attempted in your question, because such instances have a tendency to be overlapping.

Functional dependencies are less, um, weird than type families, though. All other things being equal I tend to prefer a fundep.

Community
  • 1
  • 1
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • But is it possible to create the instances for `C'` given instances for `C` (from an external library), without manually having to enumerate them? – Hjulle May 06 '17 at 16:37
  • Why do you want to do that? Can't you just write functions with a `C` constraint? – Benjamin Hodgson May 06 '17 at 16:43
  • One part because it should be possible, the functional dependency contains all the information needed for the type family. As far as I know they are equivalent, so it should be possible to convert between them. The other part is that I'm doing some strange wrestling with the type checker and believe that getting a `*` kind instead of a constraint (`forall a. A a b => a` vs `A' a`) will show the compiler that I'm not actually giving it a polymorphic value. – Hjulle May 06 '17 at 16:54
  • @Hjulle Sounds like an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) and probably warrants a separate question. In practice you can probably use a `newtype`, such as `newtype Wrapped a b = Wrapped { unWrap :: a }`, but the form that would take probably depends on what the class does exactly. – Benjamin Hodgson May 06 '17 at 16:56
  • I still want an answer to this question. As I said, one part is because I believe that it should be possible and wonder if it is. Is this the wrong forum for such questions? – Hjulle May 06 '17 at 17:01
  • 1
    @Hjulle It is possible but you have to use a newtype, as I said. See my edited answer – Benjamin Hodgson May 06 '17 at 17:14
  • Btw, overlapping instances should not be a problem, since I'm defining the class myself and I am not planning to add any other instances than that one. – Hjulle May 06 '17 at 17:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143576/discussion-between-hjulle-and-benjamin-hodgson). – Hjulle May 06 '17 at 17:30
  • Now that I think of it, wouldn't the wrapped instance `instance C a b => C' (Wrap a b) where B (Wrap a b) = b` correspond to `class C a b | a b -> b` (which is equivalent to `class C a b`) rather than `class C a b | a -> b`? You can't recover the fundep instance from a wrapped instance of `C'`. – Hjulle May 07 '17 at 04:39
  • 1
    @Hjulle I don't quite follow you. `Wrap` packages an instance of `C` into an instance of `C'`. You don't need to do any work to go back the other way because there's already an instance of `C a b`. The fundep ensures that there can only be one instance of `C'` for `Wrap` for a given `a`, namely `Wrap a b`. – Benjamin Hodgson May 07 '17 at 11:20
  • Aha, yes of course. I was thinking that it was the associated class that should enforce the dependency, but since we know that we have the FunDep class, the dependency is enforced that way. – Hjulle May 07 '17 at 12:57