kcsongor / generic-lens

Generically derive traversals, lenses, and prisms.
437 stars 53 forks source link

HasField' and nested labels don't play along well #125

Open googleson78 opened 4 years ago

googleson78 commented 4 years ago

In this example:

{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE MonoLocalBinds #-}

import Data.Generics.Product (HasField', field')
import Data.Generics.Labels ()

g :: (HasField' "outer" s t, HasField' "inner" t a) => forall f. Functor f => (a -> f a) -> s -> f s
g = #outer . #inner

g' :: (HasField' "outer" s t, HasField' "inner" t a) => forall f. Functor f => (a -> f a) -> s -> f s
g' = field' @"outer" . field' @"inner"

g' typechecks, but g doesn't:

Collapsed error

```haskell blademaster :: ~ » stack ghci generic-labels.hs --package generic-lens --package microlens [1 of 1] Compiling Main ( /home/googleson78/generic-labels.hs, interpreted ) /home/googleson78/generic-labels.hs:11:5: error: • Overlapping instances for HasField "outer" s s s0 t0 arising from the overloaded label ‘#outer’ Matching instances: instance (HasTotalFieldP field (Rep s) ~~ 'Just a, HasTotalFieldP field (Rep t) ~~ 'Just b, HasTotalFieldP field (Rep (Indexed s)) ~~ 'Just a', HasTotalFieldP field (Rep (Indexed t)) ~~ 'Just b', t ~~ Infer s a' b, s ~~ Infer t b' a, HasField0 field s t a b) => HasField field s t a b -- Defined in ‘Data.Generics.Product.Fields’ ...plus one instance involving out-of-scope types (use -fprint-potential-instances to see them all) (The choice depends on the instantiation of ‘s, s0, t0’ To pick the first instance above, use IncoherentInstances when compiling the other instance declarations) • In the first argument of ‘(.)’, namely ‘#outer’ In the expression: #outer . #inner In an equation for ‘g’: g = #outer . #inner | 11 | g = #outer . #inner | ^^^^^^ /home/googleson78/generic-labels.hs:11:14: error: • Overlapping instances for HasField "inner" s0 t0 a a arising from the overloaded label ‘#inner’ Matching instances: instance (HasTotalFieldP field (Rep s) ~~ 'Just a, HasTotalFieldP field (Rep t) ~~ 'Just b, HasTotalFieldP field (Rep (Indexed s)) ~~ 'Just a', HasTotalFieldP field (Rep (Indexed t)) ~~ 'Just b', t ~~ Infer s a' b, s ~~ Infer t b' a, HasField0 field s t a b) => HasField field s t a b -- Defined in ‘Data.Generics.Product.Fields’ ...plus one instance involving out-of-scope types (use -fprint-potential-instances to see them all) (The choice depends on the instantiation of ‘s0, t0, a’ To pick the first instance above, use IncoherentInstances when compiling the other instance declarations) • In the second argument of ‘(.)’, namely ‘#inner’ In the expression: #outer . #inner In an equation for ‘g’: g = #outer . #inner | 11 | g = #outer . #inner | ^^^^^^ ```

Is this some known issue, and can it be worked around somehow? I think Lens'/HasField' is much more understandable to someone not familiar with optics, and really prefer using it whenever possible.

friedbrice commented 4 years ago

I doubt that it's fixable. It's similar to the problem of show . read. show . read can't compile because read gives a polymorphic output while show takes a polymorphic input, and there's no way for the compiler to infer the intermediate type.

arybczak commented 4 years ago

It'll work with generic-optics (once #120 gets merged) since using % instead of . helps the compiler with type inference.