Data families vs Injective type families

前端 未结 3 699
南旧
南旧 2020-12-13 15:00

Now that we have injective type families, is there any remaining use case for using data families over type families?

Looking at past StackOverflow questions about d

相关标签:
3条回答
  • 2020-12-13 15:22

    The answer by Reid Barton explains the distinction between my two examples perfectly. It has reminded me of something I read in Richard Eisenberg's thesis about adding dependent types to Haskell and I thought that since the heart of this question is injectivity and generativity, it would be worth mentioning how DependentHaskell will deal with this (when it eventually gets implemented, and if the quantifiers proposed now are the ones eventually implemented).

    What follows is based on pages 56 and 57 (4.3.4 Matchability) of the aforementioned thesis:

    Definition (Generativity). If f and g are generative, then f a ~ g b implies f ~ g

    Definition (Injectivity). If f is injective, then f a ~ f b implies a ~ b

    Definition (Matchability). A function f is matchable iff it is generative and injective

    In Haskell as we know it now (8.0.1) the matchable (type-level) functions consist exactly of newtype, data, and data family type constructors. In the future, under DependentHaskell, one of the new quantifiers we will get will be '-> and this will be used to denote matchable functions. In other words, there will be a way to inform the compiler a type-level function is generative (which currently can only be done by making sure that function is a type constructor).

    0 讨论(0)
  • 2020-12-13 15:29

    You're missing one other detail - data families create new types. Type families can only refer to other types. In particular, every instance of a data family declares new constructors. And it's nicely generic. You can create a data instance with newtype instance if you want newtype semantics. Your instance can be a record. It can have multiple constructors. It can even be a GADT if you want.

    It's exactly the difference between the type and data/newtype keywords. Injective type families don't give you new types, rendering them useless in the case where you need that.

    I understand where you're coming from. I had this same issue with the difference initially. Then I finally ran into a use case where they're useful, even without a type class getting involved.

    I wanted to write an api for dealing with mutable cells in a few different contexts, without using classes. I knew I wanted to do it with a free monad with interpreters in IO, ST, and maybe some horrible hacks with unsafeCoerce to even go so far as shoehorning it into State. This wasn't for any practical purpose, of course - I was just exploring API designs.

    So I had something like this:

    data MutableEnv (s :: k) a ...
    
    newRef :: a -> MutableEnv s (Ref s a)
    readRef :: Ref s a -> MutableEnv s a
    writeRef :: Ref s a -> a -> MutableEnv s ()
    

    The definition of MutableEnv wasn't important. Just standard free/operational monad stuff with constructors matching the three functions in the api.

    But I was stuck on what to define Ref as. I didn't want some sort of class, I wanted it to be a concrete type as far as the type system was concerned.

    Then late one night I was out for a walk and it hit me - what I essentially want is a type whose constructors are indexed by an argument type. But it had to be open, unlike a GADT - new interpreters could be added at will. And then it hit me. That's exactly what a data family is. An open, type-indexed family of data values. I could complete the api with just the following:

    data family Ref (s :: k) :: * -> *
    

    Then, dealing with the underlying representation for a Ref was no big deal. Just create a data instance (or newtype instance, more likely) whenever an interpreter for MutableEnv is defined.

    This exact example isn't really useful. But it clearly illustrates something data families can do that injective type families can't.

    0 讨论(0)
  • 2020-12-13 15:34
    type family T a = r | r -> a
    data family D a
    

    An injective type family T satisfies the injectivity axiom

    if T a ~ T b then a ~ b

    But a data family satisfies the much stronger generativity axiom

    if D a ~ g b then D ~ g and a ~ b

    (If you like: Because the instances of D define new types that are different from any existing types.)

    In fact D itself is a legitimate type in the type system, unlike a type family like T, which can only ever appear in a fully saturated application like T a. This means

    • D can be the argument to another type constructor, like MaybeT D. (MaybeT T is illegal.)

    • You can define instances for D, like instance Functor D. (You can't define instances for a type family Functor T, and it would be unusable anyway because instance selection for, e.g., map :: Functor f => (a -> b) -> f a -> f b relies on the fact that from the type f a you can determine both f and a; for this to work f cannot be allowed to vary over type families, even injective ones.)

    0 讨论(0)
提交回复
热议问题