haskelltypesvinyl

Haskell: refer to type of value at compile time


I'm wondering if there's a nice way to refer to types of values without explicitly aliasing them using type in code (not at runtime - there is no reification going on here).

Take the following code (using Data.Vinyl):

{-# LANGUAGE DataKinds, TypeOperators #-}

import Data.Vinyl

name = Field :: "name" ::: String
age = Field :: "age" ::: Int
type Person = ["name" ::: String, "age" ::: Int]

Here we have the types "name" ::: String and "age" ::: Int repeated in two places. If we reuse fields in multiple records, this can become multiple places. Despite the fact that the Person type is really referring to the constituent fields, the type declarations are independent. So changing age to be represented by Float, say, requires changes in various places.

Obviously it's not necessary to explicitly type things, since they will be inferred. However, in my case the record types are being returned from an options parser, and thus exported. Likewise, one could write the following:

type Name = "name" ::: String
name = Field :: Name
type Age = "age" ::: Int
age = Field :: Age
type Person = [Name, Age]

However, this then involves another load of type aliases and double the number of lines. What I would like to be able to write is the following:

name = Field :: "name" ::: String
age = Field :: "age" ::: Int
type Person = [typeof name, typeof age]

This explicitly links the type of a Person to the types of its fields.

Is there a way (preferably sans-TH, but I'd be interested even involving TH) to do this?


Solution

  • It should be easy enough to make a String -> [Name] -> DecsQ function out of the following. Too bad with ghc7.6 (at least), the check for cycles in type synonyms seems to stop the prettier type Person = $(listOfT ['name, 'age]) from working out.

    {-# LANGUAGE DataKinds, TemplateHaskell, TypeOperators #-}
    import Language.Haskell.TH
    import Control.Applicative
    import Data.Vinyl
    
    name = Field :: "name" ::: String
    age = Field :: "age" ::: Int
    
    let listOfT (n:ns) = do
            VarI _ ty _ _ <- reify n
            (appT promotedConsT) (return ty) `appT` listOfT ns
        listOfT [] = promotedNilT
     in return <$> tySynD (mkName "Person") [] (listOfT ['name, 'age])