tly
tly

Reputation: 1262

Overlapping instances of a type class for a constructor

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

Answers (1)

leftaroundabout
leftaroundabout

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

Related Questions