I'm playing with an extensible record library, and I'm wanting to write a function field
that can operate as either a Lens
or a Traversal
based on whether or not the Symbol
key is in the list of keys. The type family is given:
type family LensOrTraversal key keys s t a b where
LensOrTraversal key '[] s t a b =
Traversal s t a b
LensOrTraversal key (key =: val ': xs) s t a b =
Lens s t a b
LensOrTraversal key (foo =: bar ': xs) s t a b =
LensOrTraversal key xs s t a b
This code gives me an error:
/home/matt/Projects/hash-rekt/src/Data/HashRecord/Internal.hs:433:5:
error:
• Illegal polymorphic type: Traversal s t a b
• In the equations for closed type family ‘LensOrTraversal’
In the type family declaration for ‘LensOrTraversal’
Ideally, I'd like to be able to reuse the field
name for both lenses and traversals, as it would allow you to write
>>> let testMap = empty & field @"foo" .~ 'a'
>>> :t testMap
HashRecord '["foo" =: Char]
>>> testMap ^. field @"foo"
'a'
>>> testMap ^. field @"bar"
Type error
>>> testMap ^? field @"bar"
Nothing
which follows common lens
idioms. I can provide an fieldTraversal
function that does what I want, but I'd prefer to overload the name field
if possible. How would you work around this limitation of type families?
A lens is already a traversal, only its Rank2 quantifier doesn't make use of the full constraint (it merely requires Functor
, not Applicative
).
type Lens s t a b = ∀ f . Functor f => (a -> f b) -> s -> f t
type Traversal s t a b = ∀ f . Applicative f => (a -> f b) -> s -> f t
It is at the level of this constraint that you should introduce your type family:
import GHC.Exts (Constraint)
type family FieldOpticConstraint key keys :: (* -> *) -> Constraint where
FieldOpticConstraint key '[] = Applicative
FieldOpticConstraint key (key =: val ': xs) = Functor
FieldOpticConstraint key (_ ': xs) = FieldOpticConstraint key xs
Then field
should not yield a LensOrTraversal
, but always a custom Rank2-signature with the constraint determined by the type family.