Automatic derivation of Data.Vector.Unbox with associated type synonyms
Asked Answered
A

2

7

I have a datatype

newtype Zq q = Zq (IntType q)

where 'q' will be an instance of the class

class Foo a where
   type IntType a

and 'IntType' is just the underlying representation (i.e. Int, Integral, etc) associated with 'q'.

I want to make Zq an instance of Data.Vector.Unbox. We are currently manually deriving Unbox using about 50 lines of trivial code as suggested in the link above. We will be making several different types 'Unbox' in our code, so writing 50 lines for each type is not appealing.

I found two alternatives here. One alternative is to use this package which uses Template Haskell to derive instances of Unbox. The TH code would look like:

derivingUnbox "Zq"
  [d| instance (Foo q, U.Unbox (IntType q)) => Unbox' (ZqBasic q) (IntType q) |]
  [| \ (Zq x) -> x |]
  [| \ x -> Zq x |]

The problem is, I can't define instances using associated type synonyms (or can I??)

[A related question: Why does TypeSynonymInstances, an extension implied by FlexibleInstances, not allow associated type synonym instances? Is this somehow fundamentally a different beast?]

My current solution to that problem is to redefine Zq as

newtype Zq q i = Zq i

and then add the equality constraint

i~(IntType q)

in every instance involving (Zq q i), which isn't very elegant. My (working) Unbox derivation becomes

derivingUnbox "Zq"
  [d| instance (U.Unbox i, i~IntType q, Foo q) => Unbox' (Zq q i) i |]
  [| \ (Zq x) -> x |]
  [| \ x -> Zq x |]

I feel like I should be able to accomplish this without resorting to explicitly exposing the type 'i'. All I've done is moved it from an associated type synonym to an explicit parameter with equality constraints. Why is this "fundamentally" a different (and apparently safer) approach? Is there some way I can avoid adding the type parameter 'i' and still get automatic Unbox derivation?

Extra type parameter aside, I'm having trouble using the TH package to derive Unbox for (Vector r), that is, I want to make an Unbox Vector of Unbox Vectors. My attempt is something like:

newtype Bar r = Bar (Vector r)

derivingUnbox "Bar"
  [d| instance (Unbox r) => Unbox' (Bar r) (Vector r) |]
  [| \ (Bar x) -> x |]
  [| \ x -> Bar x |]

but I get (lots of) errors like:

`basicUnsafeFreeze` is not a (visible) method of class `Data.Vector.Generic.Base.Vector`

I'm not sure why it can't find this method, when it works just fine for my Zq type.


The second approach listed above is using the extension GeneralizedNewtypeDeriving. The biggest problem I see with this approach is that I have some actual Data (rather than Newtype) that I need to be Unbox. However, just using the extension, I should be able to write

newtype Zq q = Zq (IntType q) deriving (Unbox, M.MVector MVector, G.Vector Vector)

or at least

newtype Zq q i = Zq i deriving (Unbox, M.MVector MVector, G.Vector Vector)

The first leads to the errors:

No instance for (Unbox (IntType q)) arising from the `deriving` clause of a data type declaration
No instance for (M.MVector MVector (IntType q)) ""
No instance for (G.Vector Vector (IntType q)) ""

and the second gives:

No instance for (M.MVector MVector i) ""
No instance for (G.Vector U.Vector i) ""

I'm not sure why it can't derive these instances, as the post above leads me to believe it should be able to. Perhaps I could get away with using the associated type synonym with GeneralizedNewtypeDeriving? (This still (probably) doesn't solve my problem when I need to derive Unbox for 'data's.)

Thanks for your help!

Ajmer answered 9/7, 2012 at 16:20 Comment(3)
Vector a is not, and probably should never be, an instance of Unbox. Why do you think it would make sense? Unbox types should generally be types that have a fixed-size representation in bytes, which is not the case for Vector a. The only possible alternative would be an Unbox instance that secretly doesn't do any unboxing, and uses a normal Data.Vector.Vector, but I'm not sure there's any point to that.Circumstantial
I'm not sure if this could make a difference or not, but the type "Bar r" above is really "Bar m r" in our program, and 'm' uniquely determines the size of the vector (admittedly, not in any way the compiler could tell I think). Could we use this somehow to make unbox vectors, or should we just stick with normal Vector.Vectors? Either way, the questions about the Unbox derivation for Zq still stands.Ajmer
You may be able to use that to make unboxed vectors, I suppose, but you're definitely not going to be able to automatically derive the implementation.Circumstantial
N
4

You're running into a few separate problems here:

The TH approach

Yes, class instances for associated type synonyms are illegal

It's true that you can't define class instances for associated type synonyms or type functions, and this is with good reason: the compiler can't tell whether they overlap. For instance:

type family F a
instance Eq (F Int)
instance Eq (F Bool)

Do these instances overlap? Given the above source code, we can't tell: it depends on how someone later defines instances for F. For instance, they could define

type instance F Int = Double
type instance F Bool = Double

and then the two instances of Eq would in fact be overlapping.

You're running into a problem with the vector-th-unbox package

If you look at the actual Unbox instance you want, you don't actually want an instance for IntType q; what you want is simply this:

instance (Unbox (IntType q), Foo q) => Unbox (Zq q) where
    ...

The problem is that the vector-th-unbox package forces you to use the fake type-class Unbox' to communicate the intermediate representation type (IntType q in your case), as a convenient way of abusing Template Haskell's syntax to pass in a type. And then GHC sees that you have written Unbox' (Zq q) (IntType q) and complains. I would suggest filing a bug for the vector-th-unbox package.

Unbox for Vector r

I think Louis Wasserman covered this.

The GeneralizedNewtypeDeriving approach

The specific compile error you're having is because GHC can't infer the appropriate context. For most problems similar to the one you're having, StandaloneDeriving language extension would solve your problem:

deriving instance Unbox (IntType q) => Unbox (Zq q)
deriving instance Unbox (IntType q) => M.MVector MVector (Zq q)
deriving instance Unbox (IntType q) => G.Vector Vector (Zq q)

But DON'T do this!

While GeneralizedNewtypeDeriving often does exactly what you want, it is broken in some fundamental ways, and the Unbox instance it produces is completely broken! So stick with the TH approach, after lobbying Liyang for a fix for your current problem.

Neutralism answered 10/7, 2012 at 22:10 Comment(0)
G
4

I've changed the syntax in 4820b73, so you should be able to do what you want now:

derivingUnbox "Complex"
    [d| (Unbox a) ⇒ Complex a → (a, a) |]
    [| \ (r :+ i) → (r, i) |]
    [| \ (r, i) → r :+ i |]

I've also fixed the “xyz is not a (visible) method…” errors in fe37976, although it was possible to work around it with:

import qualified Data.Vector.Generic
import qualified Data.Vector.Generic.Mutable

Out now on Hackage. CC: @reinerp

Gorged answered 18/11, 2012 at 6:35 Comment(1)
It makes me cry that I have to add 3 extensions and a package for such a seemingly simple thing, but thanks for at least providing the heavy lifting for us in a package!Modernity

© 2022 - 2024 — McMap. All rights reserved.