Reputation: 1262
I would like to create a type class GetGLInfo
that holds some basic type information needed by multiple OpenGL
functions:
class GetGLInfo v where
numComponents :: v -> GL.GLint
bytesPerComponent :: v -> Int
openGLType :: v -> GL.GLenum
An easy instance is Float
:
instance GetGLInfo Float where
numComponents _ = 1
bytesPerComponent = sizeOf
openGLType _ = GL.GL_FLOAT
I would like to create an instance of this class for the Data.Vec vector data type with the constructor data a :. b
:
import qualified Data.Vec as Vec
instance GetGLInfo a => GetGLInfo ((Vec.:.) a ()) where
numComponents _ = 1
bytesPerComponent = (bytesPerComponent).(Vec.head)
openGLType = (openGLType).(Vec.head)
instance (GetGLInfo a, GetGLInfo b) => GetGLInfo ((Vec.:.) a b) where
numComponents (_ Vec.:. r)= 1 + (numComponents r)
bytesPerComponent = (bytesPerComponent).(Vec.head)
openGLType = (openGLType).(Vec.head)
I wouldve used numComponents = Vec.length
but I got more type errors...
When calling openGLType
for a Vec2
I get the following error:
let c = openGLType ((0.0 :. 1.0 :. ()) :: Vec2 Float)
error:
* Overlapping instances for GetGLInfo (Float :. ())
arising from a use of `openGLType'
Matching instances:
instance GetGLInfo a => GetGLInfo (a :. ())
-- Defined in `GLTypeInfo'
instance (GetGLInfo a, GetGLInfo b) => GetGLInfo (a :. b)
-- Defined in `GLTypeInfo'
* In the expression: openGLType ((0.0 :. 1.0 :. ()) :: Vec2 Float)
In an equation for `c':
c = openGLType ((0.0 :. 1.0 :. ()) :: Vec2 Float)
Why do my instances overlap here?
Is there a easy way to create an instance of (:.) a b
without an explicit instance for the base case (:.) a ()
?
Upvotes: 0
Views: 89
Reputation: 120711
You must make the inductive instance actually distinguishable from the base case. Right now, ((Vec.:.) a ())
is merely a special case of ((Vec.:.) a b)
, which is what “overlapping instance” means. But you want the latter to only match if b
is itself already non-zero-dimensional. Well, make that explicit:
instance (GetGLInfo a, GetGLInfo b, GetGLInfo xs) => GetGLInfo (a:.b:.xs) where
numComponents (_:.b:.xs)= 1 + numComponents (b:.xs)
bytesPerComponent = bytesPerComponent . Vec.head
openGLType = openGLType . Vec.head
Alternatively and in principle more elegant is what Alexey Romanov suggests: make the base case zero-dimensional. But apparently there is no GL_VOID
, so not an option here.
As a completely different approach, I would suggest only making your own class for scalars, and then generalising over arbitrary vector spaces:
import Data.VectorSpace
import Data.Basis
class GLScalar s where
scalar_bytesPerComponent :: Functor proxy => proxy s -> Int
scalar_openGLType :: Functor proxy => proxy s -> GL.GLenum
instance GLScalar Float
bytesPerComponent :: ∀ v proxy
. (VectorSpace v, GLScalar (Scalar v), Functor proxy)
=> proxy v -> Int
bytesPerComponent _ = scalar_bytesPerComponent ([]::[Scalar v])
numComponents :: ∀ v . HasBasis v => v -> Int
numComponents = length . decompose
Upvotes: 1