diff --git a/LICENCE b/LICENCE
new file mode 100644
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,31 @@
+Original work copyright (c) 2013, Anthony Cowley
+Modifications copyright (c) 2015, Sebastián Méndez
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Anthony Cowley nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/record-gl.cabal b/record-gl.cabal
new file mode 100644
--- /dev/null
+++ b/record-gl.cabal
@@ -0,0 +1,50 @@
+name:                record-gl
+version:             0.1.0.0
+synopsis:            Utilities for working with OpenGL's GLSL shading language and Nikita Volkov's "Record"s.
+description:         Using Nikita Volkov's "Record" records
+                     to carry GLSL uniform parameters and vertex data enables
+                     library code to reflect over the types of the data to
+                     facilitate interaction between Haskell and GLSL. See the
+                     @examples@ directory in the repository for more.
+license:             BSD3
+license-file:        LICENCE
+author:              Sebastián Méndez
+maintainer:          sebas.chinoir@gmail.com
+copyright:           Original work copyright (C) 2013 Anthony Cowley, modifications copyright (C) 2015 Sebastián Méndez
+category:            Graphics
+build-type:          Simple
+-- extra-source-files:
+cabal-version:       >=1.10
+source-repository head
+  type:     git
+  location: http://github.com/rabipelais/record-gl
+
+library
+  exposed-modules:     Graphics.RecordGL
+                     , Graphics.RecordGL.Vertex
+                     , Graphics.RecordGL.Uniforms
+                     , Record.Introspection
+--other-modules
+--  ghc-options:         -ddump-splices
+  build-depends:       GLUtil >= 0.6.4
+                     , OpenGL >= 2.8
+                     , base >=4.6 && <5
+                     , base-prelude
+                     , containers >= 0.5
+                     , linear >= 1.1.3
+                     , record >= 0.3
+                     , tagged >= 0.4
+                     , template-haskell
+                     , vector >= 0.10
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+test-suite tests
+  hs-source-dirs: tests
+  type: exitcode-stdio-1.0
+  main-is: BasicTests.hs
+  ghc-options: -Wall -O2
+  default-language: Haskell2010
+  build-depends: base >= 4.6 && < 5,
+                 test-framework, test-framework-hunit, HUnit,
+                 linear, record, record-gl, OpenGL, tagged
diff --git a/src/Graphics/RecordGL.hs b/src/Graphics/RecordGL.hs
new file mode 100644
--- /dev/null
+++ b/src/Graphics/RecordGL.hs
@@ -0,0 +1,7 @@
+-- |
+
+module Graphics.RecordGL ( module Graphics.RecordGL.Vertex
+                         , module Graphics.RecordGL.Uniforms) where
+
+import           Graphics.RecordGL.Uniforms
+import           Graphics.RecordGL.Vertex
diff --git a/src/Graphics/RecordGL/Uniforms.hs b/src/Graphics/RecordGL/Uniforms.hs
new file mode 100644
--- /dev/null
+++ b/src/Graphics/RecordGL/Uniforms.hs
@@ -0,0 +1,210 @@
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE ConstraintKinds     #-}
+{-# LANGUAGE NoImplicitPrelude   #-}
+{-# LANGUAGE RankNTypes          #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell     #-}
+
+-- | Tools for binding vinyl records to GLSL program uniform
+-- parameters. The most common usage is to use the 'setUniforms'
+-- function to set each field of a 'Record' to the GLSL uniform
+-- parameter with the same name. This verifies that each field of the
+-- record corresponds to a uniform parameter of the given shader
+-- program, and that the types all agree.
+module Graphics.RecordGL.Uniforms (
+    -- * Operations for binding uniform values
+    setAllUniforms, setSomeUniforms, setUniforms,
+    -- * Useful type classes for setting and verifying fields
+    HasFieldGLTypes(..), UniformFields, SetUniformFields) where
+
+import           BasePrelude               hiding (Proxy)
+import qualified Data.Map                  as M
+import           Graphics.GLUtil           (AsUniform (..),
+                                            HasVariableType (..),
+                                            ShaderProgram (..))
+import           Graphics.Rendering.OpenGL (UniformLocation)
+import qualified Graphics.Rendering.OpenGL as GL
+import           Language.Haskell.TH
+
+import qualified Data.Set                  as S
+import           Record.Introspection
+import           Record.Types
+
+-- | Provide the 'GL.VariableType' of each field in a 'Record'. The list
+-- of types has the same order as the fields of the 'Record'.
+class HasFieldGLTypes a where
+    fieldGLTypes :: a -> [GL.VariableType]
+
+-- | Zips up lists of 'UniformLocation's and a 'Record' setting
+-- uniform parameters using the record fields.
+class SetUniformFields a where
+  setUniformFields :: [Maybe UniformLocation] -> a -> IO ()
+
+type UniformFields a = (HasFieldNames a, HasFieldGLTypes a, SetUniformFields a)
+
+-- | Set GLSL uniform parameters from a 'Record'. A check is
+-- performed to verify that /all/ uniforms used by a program are
+-- represented by the record type. In other words, the record is a
+-- superset of the parameters used by the program.
+setAllUniforms :: forall record. UniformFields record
+            => ShaderProgram -> record -> IO ()
+setAllUniforms s r = case checks of
+                      Left msg -> error msg
+                      Right _ -> setUniformFields locs r
+  where
+    fnames = fieldNames r
+    checks = do
+      namesCheck "record" (M.keys $ uniforms s) fnames
+      typesCheck True (snd <$> uniforms s) fieldTypes
+    fieldTypes = M.fromList $ zip fnames (fieldGLTypes r)
+    locs = map (fmap fst . (`M.lookup` uniforms s)) fnames
+
+-- | Set GLSL uniform parameters form a `Record` representing
+-- a subset of all uniform parameters used by a program.
+setUniforms :: forall record. UniformFields record => ShaderProgram -> record -> IO ()
+setUniforms s r = case checks of
+                   Left msg -> error msg
+                   Right _ -> setUniformFields locs r
+  where
+    fnames = fieldNames r
+    checks = do
+      namesCheck "GLSL programme" fnames (M.keys $ uniforms s)
+      typesCheck False fieldTypes (snd <$> uniforms s)
+    fieldTypes = M.fromList $ zip fnames (fieldGLTypes r)
+    locs = map (fmap fst . (`M.lookup` uniforms s)) fnames
+
+-- | Set GLSL uniform parameters from those fields of a 'PlainRec'
+-- whose names correspond to uniform parameters used by a program.
+setSomeUniforms :: forall r. UniformFields r
+                => ShaderProgram -> r -> IO ()
+setSomeUniforms s r = case typesCheck' True (snd <$> uniforms s) fieldTypes of
+                       Left msg -> error msg
+                       Right _ -> setUniformFields locs r
+  where
+    fnames = fieldNames r
+    fieldTypes = M.fromList . zip fnames $ fieldGLTypes r
+    locs = map (fmap fst . (`M.lookup` uniforms s)) fnames
+
+-- | @namesCheck culprit little big@ checks that each name in @little@ is
+-- an element of @big@.
+namesCheck :: String -> [String] -> [String] -> Either String ()
+namesCheck culprit little big = mapM_ go little
+  where
+    big' = S.fromList big
+    go x | x `S.member` big' = Right ()
+         | otherwise = Left $ "Field " ++ x ++ " not found in " ++ culprit
+
+-- | @typesChecks blame little big@ checks that each (name,type) pair
+-- in @little@ is a member of @big@.
+typesCheck :: Bool
+           -> M.Map String GL.VariableType -> M.Map String GL.VariableType
+           -> Either String ()
+typesCheck blame little big = mapM_ go $ M.toList little
+  where
+    go (n, t) | (glTypeEquiv t <$> M.lookup n big) == Just True = return ()
+              | otherwise = Left $ msg n (show t) (maybe "" show (M.lookup n big))
+    msg n t t' = let (expected, actual) = if blame then (t, t') else (t', t)
+                 in "Record and GLSL types disagree on field " ++ n ++
+                    ": GLSL expected " ++ expected ++
+                    ", record disappointed with " ++ actual
+
+-- | @typesCheck' blame little big@ checks that each (name,type) pair
+-- in the intersection of @little@ and @big@ is consistent.
+typesCheck' :: Bool
+           -> M.Map String GL.VariableType -> M.Map String GL.VariableType
+           -> Either String ()
+typesCheck' blame little big = mapM_ go $ M.toList little
+  where
+    go (n, t) | fromMaybe True (glTypeEquiv t <$> M.lookup n big) = return ()
+              | otherwise = Left $ msg n (show t) (maybe "" show (M.lookup n big))
+    msg n t t' = let (expected, actual) = if blame then (t, t') else (t', t)
+                 in "Record and GLSL types disagree on field " ++ n ++
+                    ": GLSL expected " ++ expected ++
+                    ", record disappointed with " ++ actual
+
+-- We define our own equivalence relation on types because we don't
+-- have unique Haskell representations for every GL type. For example,
+-- the GLSL sampler types (e.g. Sampler2D) are just GLint in Haskell.
+glTypeEquiv :: GL.VariableType -> GL.VariableType -> Bool
+glTypeEquiv x y = glTypeEquiv' x y || glTypeEquiv' y x
+
+-- The equivalence on 'GL.VariableType's we need identifies Samplers
+-- with Ints because this is how GLSL represents samplers.
+glTypeEquiv' :: GL.VariableType -> GL.VariableType -> Bool
+glTypeEquiv' GL.Sampler1D GL.Int' = True
+glTypeEquiv' GL.Sampler2D GL.Int' = True
+glTypeEquiv' GL.Sampler3D GL.Int' = True
+glTypeEquiv' x y = x == y
+
+
+-- Template Haskell instances. Here be dragons, beware... -----------------------
+
+-- I would rather have written this by hand and some Emacs macros...
+-- Instances for the HasFieldGLTypes class
+-- Example implementation:
+-- fieldGLTypes _ = [variableType (undefined::v1),...,variableType (undefined::vn)]
+return $ flip map [1..24] $ \arity ->
+  let typeName = mkName $ "Record" <> show arity
+      recordType = foldl (\a i -> AppT (AppT a (VarT (mkName ("n" <> show i))))
+                                       (VarT (mkName ("v" <> show i))))
+                         (ConT typeName)
+                         [1 .. arity]
+      vVals = map (\i -> "v" <> show i) [1..arity]
+      vTVals = map (VarT . mkName) vVals
+#if MIN_VERSION_template_haskell(2,10,0)
+      mkContext c t = AppT (ConT (mkName c)) t
+#else
+      mkContext c t = ClassP (mkName c) [t]
+#endif
+      context = map (\v -> mkContext "HasVariableType" v) vTVals
+      typesList = ListE $ map (AppE (VarE (mkName "variableType")) .
+                                    SigE (VarE (mkName "undefined")))
+                          vTVals
+      fieldGLTypesFun = FunD (mkName "fieldGLTypes")
+                             [Clause [WildP] (NormalB typesList) []]
+  in InstanceD context
+               (AppT (ConT (mkName "HasFieldGLTypes")) recordType)
+               [fieldGLTypesFun]
+
+-- Instances for the SetUniformFields class
+-- Example implementation:
+-- setUniformFields (u1 : ...) (RecordX v1...) = traverse_ (asUniform v1) u1 >> ...
+return $ flip map [1..24] $ \arity ->
+    let typeName = mkName $ "Record" <> show arity
+        recordType = foldl (\a i -> AppT (AppT a (VarT (mkName ("n" <> show i))))
+                                         (VarT (mkName ("v" <> show i))))
+                           (ConT typeName)
+                           [1 .. arity]
+        typePattern = ConP typeName (map (\i -> VarP (mkName ("v" <> show i))) [1..arity])
+        vNames = map (\i -> mkName $ "v" <> show i) [1..arity]
+        vTVals = map VarT vNames
+#if MIN_VERSION_template_haskell(2,10,0)
+        mkContext c t = AppT (ConT (mkName c)) t
+#else
+        mkContext c t = ClassP (mkName c) [t]
+#endif
+        context = map (\v -> mkContext "AsUniform" v) vTVals
+        nameE = VarE . mkName
+        uniformNames = map (\i -> mkName ("u" <> show i)) [1..arity + 1]
+        uniformPat = AsP (mkName "us") $ foldr1 (\i a -> InfixP i (mkName ":") a) $ map VarP uniformNames
+        asUniforms = foldr1 (\i a -> InfixE (Just i) (nameE ">>") (Just a)) $ zipWith go us vs
+          where
+            us = map VarE uniformNames
+            go u v = AppE (AppE (nameE "traverse_")
+                                (AppE (nameE "asUniform") v))
+                          u
+            vs = map VarE vNames
+        wrongSizes = NormalG $ InfixE (Just (AppE (nameE "length")
+                                                  (nameE "us")))
+                                      (VarE (mkName "<"))
+                                      (Just (LitE (IntegerL arity))) -- I wish this could be done with types
+        contrarily = NormalG $ nameE "otherwise"
+        setUniformFieldsFun =
+            FunD (mkName "setUniformFields")
+                 [Clause [uniformPat, typePattern]
+                  (GuardedB [(wrongSizes, AppE (nameE "error")
+                                               (LitE (StringL "Not enough UniformLocations :(")))
+                            ,(contrarily, asUniforms)]) []]
+    in InstanceD context
+                 (AppT (ConT (mkName "SetUniformFields")) recordType)
+                 [setUniformFieldsFun]
diff --git a/src/Graphics/RecordGL/Vertex.hs b/src/Graphics/RecordGL/Vertex.hs
new file mode 100644
--- /dev/null
+++ b/src/Graphics/RecordGL/Vertex.hs
@@ -0,0 +1,177 @@
+{-# LANGUAGE CPP                   #-}
+{-# LANGUAGE ConstraintKinds       #-}
+{-# LANGUAGE DataKinds             #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoImplicitPrelude     #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TypeFamilies          #-}
+{-# LANGUAGE TypeOperators         #-}
+
+-- | Utilities for working with vertex buffer objects (VBOs) filled
+-- with vertices represented as `Records`.
+module Graphics.RecordGL.Vertex (bufferVertices, bindVertices, reloadVertices
+                                , deleteVertices, enableVertices, enableVertices'
+                                , enableVertexFields, fieldToVAD
+                                , ViableVertex, BufferedVertices(..)) where
+
+import           Graphics.RecordGL.Uniforms
+import           Record.Introspection
+import           Record.Types
+
+import           BasePrelude
+import qualified Data.Map                   as M
+import           Data.Proxy
+import qualified Data.Vector.Storable       as V
+import           Foreign.Ptr                (plusPtr)
+import           Foreign.Storable
+import           GHC.TypeLits
+import           Graphics.GLUtil            hiding (Elem, throwError)
+import           Graphics.Rendering.OpenGL  (BufferTarget (..),
+                                             VertexArrayDescriptor (..),
+                                             bindBuffer, ($=))
+import qualified Graphics.Rendering.OpenGL  as GL
+
+-- | Representation of a VBO whose type describes the vertices.
+data BufferedVertices a  =
+  BufferedVertices {getVertexBuffer :: GL.BufferObject}
+
+-- | Load vertex data into a GPU-accessible buffer.
+bufferVertices :: (Storable rs, BufferSource (v rs)) => v rs -> IO (BufferedVertices rs)
+bufferVertices = fmap BufferedVertices . fromSource ArrayBuffer
+
+-- | Reload 'BufferedVertices' with a 'V.Vector' of new vertex data.
+reloadVertices :: Storable rs
+               => BufferedVertices rs
+               -> V.Vector rs
+               -> IO ()
+reloadVertices b v = do
+  bindBuffer ArrayBuffer $= Just (getVertexBuffer b)
+  replaceVector ArrayBuffer v
+
+-- | Delete the object name associated with 'BufferedVertices'
+deleteVertices :: BufferedVertices a -> IO ()
+deleteVertices = GL.deleteObjectNames . (: []) . getVertexBuffer
+
+-- | Bind previously-buffered vertex data.
+bindVertices :: BufferedVertices a -> IO ()
+bindVertices = (bindBuffer ArrayBuffer $=) . Just . getVertexBuffer
+
+-- | Constraint alias capturing the requirements of a vertex type.
+type ViableVertex t = (HasFieldNames t, HasFieldSizes t, HasFieldDims t,
+                       HasFieldGLTypes t, Storable t)
+
+-- | Line up a shader's attribute inputs with a vertex record. This
+-- maps vertex fields to GLSL attributes on the basis of record field names
+-- on the Haskell side, and variable names on the GLSL side.
+enableVertices :: forall f r. ViableVertex r
+               => ShaderProgram -> f r -> IO (Maybe String)
+enableVertices s _ = enableAttribs s (Proxy :: Proxy r)
+
+-- | Behaves like 'enableVertices', but raises an exception if the
+-- supplied vertex record does not include a field required by the
+-- shader.
+enableVertices' :: forall f r. ViableVertex r
+               => ShaderProgram -> f r -> IO ()
+enableVertices' s _ = enableAttribs s (Proxy::Proxy r) >>=
+                      maybe (return ()) error
+
+data FieldDescriptor = FieldDescriptor { fieldName   :: String
+                                       , fieldOffset :: Int
+                                       , fieldDim    :: Int
+                                       , fieldType   :: GL.VariableType }
+                       deriving Show
+
+fieldDescriptors :: ViableVertex t => t -> [FieldDescriptor]
+fieldDescriptors x = getZipList $
+                     FieldDescriptor <$> zl (fieldNames x)
+                                     <*> zl (scanl (+) 0 $ fieldSizes x)
+                                     <*> zl (fieldDims x)
+                                     <*> zl (fieldGLTypes x)
+  where zl = ZipList
+
+-- | Bind some of a shader's attribute inputs to a vertex record. This
+-- is useful when the inputs of a shader are split across multiple
+-- arrays.
+enableVertexFields :: forall p r. ViableVertex r
+                   => ShaderProgram -> p r -> IO ()
+enableVertexFields s _ = enableSomeAttribs s p >>= maybe (return ()) error
+  where
+    p = Proxy::Proxy r
+
+-- | Do not raise an error is some of a shader's inputs are not bound
+-- by a vertex record.
+enableSomeAttribs :: forall v. ViableVertex v
+                  => ShaderProgram -> Proxy v -> IO (Maybe String)
+enableSomeAttribs s p = go $ fieldDescriptors (undefined::v)
+  where go [] = return Nothing
+        go (fd:fds) =
+          let n = fieldName fd
+              shaderAttribs = attribs s
+          in case M.lookup n shaderAttribs of
+               Nothing -> return (Just $ "Unexpected attribute " ++ n)
+               Just (_,t)
+                 | fieldType fd == t -> do enableAttrib s n
+                                           setAttrib s n GL.ToFloat $
+                                             descriptorVAD p fd
+                                           go fds
+                 | otherwise -> return . Just $ "Type mismatch in " ++ n
+
+enableAttribs :: forall v. ViableVertex v
+              => ShaderProgram -> Proxy v -> IO (Maybe String)
+enableAttribs s p = go (map (second snd) $ M.assocs (attribs s))
+  where
+    go [] = return Nothing
+    go ((l, t):as) = case find ((== l) . fieldName) fs of
+                       Nothing -> return (Just $ "GLSL expecting " ++ l)
+                       Just fd
+                         | fieldType fd == t -> do
+                           enableAttrib s l
+                           setAttrib s l GL.ToFloat $
+                             descriptorVAD p fd
+                           go as
+                         | otherwise -> return . Just $ "Type mismatch in " ++ l
+    fs = fieldDescriptors (undefined::v)
+
+descriptorVAD :: forall t a. Storable t
+              => Proxy t -> FieldDescriptor -> VertexArrayDescriptor a
+descriptorVAD _ fd = VertexArrayDescriptor (fromIntegral $ fieldDim fd)
+                                           (variableDataType $ fieldType fd)
+                                           (fromIntegral $
+                                            sizeOf (undefined::t))
+                                           (offset0 `plusPtr` fieldOffset fd)
+
+namesAndOffsets :: (HasFieldNames t, HasFieldSizes t) => t -> [(String, Int)]
+namesAndOffsets x = zip (fieldNames x) (scanl (+) 0 (fieldSizes x))
+
+-- | Produce a 'GL.VertexArrayDescriptor' for a particular field of a
+-- vertex record.
+-- fieldToVAD :: forall r v a sy proxy.
+--               (
+--                Foldable v)
+--            => String -> proxy r -> GL.VertexArrayDescriptor a
+fieldToVAD :: forall sy r v a proxy.
+              (Field' sy r (v a), HasFieldNames r, HasFieldSizes r, HasGLType a, Storable r, Num (v a),
+#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707
+               KnownSymbol sy,
+#else
+               SingI sy,
+#endif
+               Foldable v)
+           => FieldName sy -> r -> GL.VertexArrayDescriptor a
+fieldToVAD _ _ = GL.VertexArrayDescriptor dim
+                                          (glType (undefined::a))
+                                          (fromIntegral sz)
+                                          (offset0 `plusPtr` offset)
+  where
+    sz = sizeOf (undefined::r)
+    dim = getSum $ foldMap (const (Sum 1)) (0::v a)
+    Just offset = lookup n $ namesAndOffsets (undefined::r)
+#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707
+    n = symbolVal (Proxy::Proxy sy)
+#else
+    n = fromSing (sing::Sing sy)
+#endif
diff --git a/src/Record/Introspection.hs b/src/Record/Introspection.hs
new file mode 100644
--- /dev/null
+++ b/src/Record/Introspection.hs
@@ -0,0 +1,124 @@
+{-# LANGUAGE CPP                 #-}
+{-# LANGUAGE ConstraintKinds     #-}
+{-# LANGUAGE DataKinds           #-}
+{-# LANGUAGE FlexibleContexts    #-}
+{-# LANGUAGE FlexibleInstances   #-}
+{-# LANGUAGE GADTs               #-}
+{-# LANGUAGE KindSignatures      #-}
+{-# LANGUAGE NoImplicitPrelude   #-}
+{-# LANGUAGE RankNTypes          #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TemplateHaskell     #-}
+{-# LANGUAGE TypeFamilies        #-}
+{-# LANGUAGE TypeOperators       #-}
+
+-- | Introspect field names and other attributes of a "Record".
+module Record.Introspection  where
+
+import           BasePrelude         hiding (Proxy)
+#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707
+import           Data.Proxy
+#endif
+import           Foreign.Storable    (Storable (sizeOf))
+import           GHC.TypeLits
+import           Language.Haskell.TH
+import           Record.Types
+
+-- | List all field names in a record.
+class HasFieldNames a where
+    fieldNames :: a -> [String]
+
+return $ flip map [1..24] $ \arity ->
+    let typeName = mkName $ "Record" <> show arity
+        recordType = foldl (\a i -> AppT (AppT a (SigT (VarT (mkName ("n" <> show i))) (ConT ''Symbol)))
+                                         (VarT (mkName ("v" <> show i))))
+                           (ConT typeName)
+                           [1 .. arity]
+        nVals = map (\i -> "n" <> show i) [1..arity]
+        nTVals = map (VarT . mkName) nVals
+#if MIN_VERSION_template_haskell(2,10,0)
+        mkContext c t = AppT (ConT (mkName c)) t
+#else
+        mkContext c t = ClassP (mkName c) [t]
+#endif
+        fieldNames' = ListE $ flip map nTVals
+#if defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ >= 707
+                    (\n -> AppE (VarE (mkName "symbolVal"))
+                           (SigE (ConE (mkName "Proxy")) (AppT (ConT (mkName "Proxy")) n)))
+        context = map (\n -> mkContext "KnownSymbol" n) nTVals
+#else
+                    (\n -> AppE (VarE (mkName "fromSing"))
+                           (SigE (VarE (mkName "sing")) (AppT (ConT (mkName "Sing")) n)))
+        context = map (\n -> mkContext "SingI" n) nTVals
+#endif
+        fieldNamesFun = FunD (mkName "fieldNames")
+                             [Clause [WildP] (NormalB fieldNames') []]
+    in InstanceD context
+                 (AppT (ConT (mkName "HasFieldNames")) recordType)
+                 [fieldNamesFun]
+
+-- | Compute the dimensionality of each field in a record. This is
+-- primarily useful for things like the small finite vector types
+-- provided by "Linear".
+class HasFieldDims a where
+    fieldDims :: a -> [Int]
+
+return $ flip map [1..24] $ \arity ->
+    let typeName = mkName $ "Record" <> show arity
+        recordType = foldl (\a i -> AppT (AppT a (SigT (VarT (mkName ("n" <> show i))) (ConT ''Symbol)))
+                                         (AppT (VarT (mkName ("l" <> show i))) (VarT (mkName ("v" <> show i)))))
+                           (ConT typeName)
+                           [1 .. arity]
+        vVals = map (\i -> "v" <> show i) [1..arity]
+        vTVals = map (VarT . mkName) vVals
+        lVals =  map (\i -> "l" <> show i) [1..arity]
+        lTVals = map (VarT . mkName) lVals
+        vectorTVals = zipWith AppT lTVals vTVals
+        numCxt = map (\i -> mkContext "Num" i) vectorTVals
+        foldCxt = map (\i -> mkContext "Foldable" i) lTVals
+        context = foldCxt ++ numCxt
+        nameE = VarE . mkName
+#if MIN_VERSION_template_haskell(2,10,0)
+        mkContext c t = AppT (ConT (mkName c)) t
+#else
+        mkContext c t = ClassP (mkName c) [t]
+#endif
+        fieldDims' = ListE $ flip map vectorTVals
+                   (\v -> nameE "getSum" `AppE`
+                         (nameE "foldMap" `AppE`
+                          (nameE "const" `AppE`
+                           (ConE (mkName "Sum") `AppE` (LitE (IntegerL 1)))) `AppE`
+                         (SigE (LitE (IntegerL 0)) v)))
+        fieldDimsFun = FunD (mkName "fieldDims")
+                            [Clause [WildP]
+                            (NormalB fieldDims') []]
+    in InstanceD context
+                 (AppT (ConT (mkName "HasFieldDims")) recordType)
+                 [fieldDimsFun]
+
+-- | Compute the size in bytes of of each field in a record.
+class HasFieldSizes a where
+  fieldSizes :: a -> [Int]
+
+return $ flip map [1..24] $ \arity ->
+    let typeName = mkName $ "Record" <> show arity
+        recordType = foldl (\a i -> AppT (AppT a (SigT (VarT (mkName ("n" <> show i))) (ConT ''Symbol)))
+                                         (VarT (mkName ("v" <> show i))))
+                           (ConT typeName)
+                           [1 .. arity]
+        vVals = map (\i -> "v" <> show i) [1..arity]
+        vTVals = map (VarT . mkName) vVals
+#if MIN_VERSION_template_haskell(2,10,0)
+        mkContext c t = AppT (ConT (mkName c)) t
+#else
+        mkContext c t = ClassP (mkName c) [t]
+#endif
+        context = map (\v -> mkContext "Storable" v) vTVals
+        fieldSizes' = ListE $ flip map vTVals
+                    (\v -> AppE (VarE (mkName "sizeOf"))
+                           (SigE (VarE (mkName "undefined")) v))
+        fieldSizesFun = FunD (mkName "fieldSizes")
+                             [Clause [WildP] (NormalB fieldSizes') []]
+    in InstanceD context
+                 (AppT (ConT (mkName "HasFieldSizes")) recordType)
+                 [fieldSizesFun]
diff --git a/tests/BasicTests.hs b/tests/BasicTests.hs
new file mode 100644
--- /dev/null
+++ b/tests/BasicTests.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE DataKinds     #-}
+{-# LANGUAGE QuasiQuotes   #-}
+{-# LANGUAGE TypeOperators #-}
+
+import           Data.Word
+import           Foreign.Ptr                    (nullPtr, plusPtr)
+import           Graphics.RecordGL
+import           Graphics.Rendering.OpenGL      (DataType (..), GLfloat,
+                                                 VertexArrayDescriptor (..))
+import           Linear                         (V1 (..), V3 (..))
+import           Record
+import           Record.Types
+import           Test.Framework                 (defaultMain)
+import           Test.Framework.Providers.HUnit (hUnitTestToTests)
+import           Test.HUnit                     (Test (..), (~=?))
+
+type Vertex = [r| {vpos :: V3 GLfloat, tagByte :: V1 Word8} |]
+
+testVad :: Test
+testVad = TestLabel "Sample VAD Creation" $
+          vad ~=? fieldToVAD (undefined::FieldName "vpos") (undefined::Vertex)
+  where vad = VertexArrayDescriptor 3 Float 13 (nullPtr `plusPtr` 1)
+
+main :: IO ()
+main = defaultMain . hUnitTestToTests $ testVad
