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.
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.