haskellpurescriptrecordtyperow-polymorphism

infer a type for common fields in two records


Bear with me if this is a foolish question. How can I type a generic function that takes two records and returns an array of their common fields?

Let's say I have:

type A = { name :: String, color :: String }
type B = { name :: String, address :: Address, color :: String }

myImaginaryFunction :: ???
-- should return ["name", "color"] :: Array of [name color]

I want to write a function that takes ANY two types of records and return an array of common fields. A haskell solution would work as well.


Solution

  • To express two record types with common fields in Haskell, you'll need a GHC extension:

    {-# LANGUAGE DuplicateRecordFields #-}
    

    and to introspect the names of the fields, you'll need generics based on the Data class:

    {-# LANGUAGE DeriveDataTypeable #-}
    import Data.Data ( Data, Typeable, DataRep(AlgRep), dataTypeRep
                     , dataTypeOf, constrFields)
    import Data.List (intersect)
    import Data.Proxy (Proxy(..), asProxyTypeOf)
    

    This will allow you to define two data types using the same field names:

    data Address = Address String deriving (Typeable, Data)
    data A = A { name :: String, color :: String }
        deriving (Typeable, Data)
    data B = B { name :: String, address :: Address, color :: String}
        deriving (Typeable, Data)
    

    and then you can retrieve the field names using:

    fieldNames :: (Data t) => Proxy t -> [String]
    fieldNames t = case dataTypeRep $ dataTypeOf $ asProxyTypeOf undefined t of
      AlgRep [con] -> constrFields con
    

    and get the common fields with:

    commonFields :: (Data t1, Data t2) => Proxy t1 -> Proxy t2 -> [String]
    commonFields t1 t2 = intersect (fieldNames t1) (fieldNames t2)
    

    After which the following will work:

    ghci> commonFields (Proxy :: Proxy A) (Proxy :: Proxy B)
    ["name", "color"]
    ghci>
    

    Note that the implementation of fieldNames above assumes that only record types with a single constructor will be introspected. See the documentation for Data.Data if you want to generalize it.

    Now, because you're a help vampire, I know that you will demand a type level function, even though you said nothing in your question about requiring a type-level function! In fact, I can see you've already added a comment about how you're interested in somehow returning an array of name | color though no such thing exists in Haskell and even though you explicitly said in your question that you expected the term-level answer ["name", "color"].

    Still, there may be non-vampires with a similar question, and perhaps this answer will help them instead.