I mistakenly believed that it was impossible to do in Haskell (because having a “disjunction” of constraints is tricky) but after reading section 3.1 of Hiromi Ishii’s functional pearl “Witness Me — Constructive Arguments Must Be Guided with Concrete Witness” turns out it’s doable!

Here’s a gist with an example. The gist defines a helper datatype `data RU r1 r2 = RU r1 r2`

for the “union” of two records, along with a suitable `HasField`

instance.

First, we must define a type-level binary search tree:

```
data Map symbol q
= E
| N (Map symbol q) symbol q (Map symbol q)
deriving (Show, Eq)
```

Along with some helper type families. Here are their kind signatures:

```
type Insert :: Symbol -> q -> Map Symbol q -> Map Symbol q
-- entries in the first map win in collisions
type Combine :: Map Symbol q -> Map Symbol q -> Map Symbol q
type Find :: Symbol -> Map Symbol q -> q
-- makes all entries in a map equal to some "value" of kind q
type Overwrite :: q -> Map symbol z -> Map symbol q
-- gets the field map of a conventional record, using Generics
type RecordCode :: Type -> Map Symbol Type
```

Having done that, the next step is to define a version of `HasField`

which takes an extra parameter of kind `Location`

, which tells us where to find the field:

```
data Location =
InFirst
| InSecond
type UnionHasField :: Location -> Symbol -> Type -> Type -> Constraint
class UnionHasField location x r a | location x r -> a where
getFieldU :: r -> a
```

Here are the instances for `RU`

:

```
instance HasField k r1 a => UnionHasField InFirst k (RU r1 r2) a where
getFieldU (RU r1 _) = getField @k r1
instance HasField k r2 a => UnionHasField InSecond k (RU r1 r2) a where
getFieldU (RU _ r2) = getField @k r2
```

The final step is to define a `HasField`

instance for `RU`

which delegates to `UnionHasField`

. And, while delegating, we must also calculate the `Location`

of each field of the union:

```
instance
UnionHasField
(Find k (Overwrite InFirst (RecordCode r1) `Combine` (Overwrite InSecond (RecordCode r2))))
k
(RU r1 r2)
a =>
HasField k (RU r1 r2) a
where
getField r = getFieldU @(Find k (Overwrite InFirst (RecordCode r1) `Combine` (Overwrite InSecond (RecordCode r2)))) @k r
```

We obtain the map of fields of `r1`

, and we make all the “values” equal to `InFirst`

. The same for `r2`

but with `InSecond`

. Then we combine the maps and find the record in which we should find the field `k`

.

(It’s a bit annoying that we must write that complicated type family expression two times, one in the constraints and one in the type application. It might even be slower to compile. How to avoid that?)

Now we can do something like:

```
data Person = Person {name :: String, age :: Int}
deriving (Show, G.Generic)
data Address = Address {street :: String, number :: Int}
deriving (Show, G.Generic)
personAddress = RU (Person "John" 50) (Address "Rue del Percebe" 2)
main :: IO ()
main = do
print $ getField @"name" personAddress
print $ getField @"age" personAddress
print $ getField @"street" personAddress
print $ getField @"number" personAddress
```