1

Asking ghci about instances of a record type R does not return the instances of HasField.

data R = MkR {f1 :: Int, f2 :: Int}

instance HasField "f3" R Int where
  getField (MkR a b) = a + b

-- ghci
>:i R
type R :: *
data R = MkR {f1 :: Int, f2 :: Int}
-- I'd expect instances for HasField <f1 | f2 | f3>
-- But they aren't in the output

I assume is due to the multiparam nature of such a class, so I tried to write a template haskell snnipet following this other SO answer. The thing is that I am unable to make it work... It just return the empty list.

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DataKinds #-}

import Language.Haskell.TH
    ( stringL,
      Exp(LitE),
      Q,
      Type(ConT),
      Name,
      InstanceDec,
      reifyInstances )
import GHC.Records (HasField (getField))

getFieldIntances :: Name -> Q [InstanceDec]
getFieldIntances name = reifyInstances ''HasField  [ConT name] -- Is this even correct?? I guess ConT is the constructor for "type constructors"

showFieldInstances :: Name -> Q Exp
showFieldInstances name = LitE . stringL . show <$> getFieldIntances name

data R = MkR {f1 :: Int, f2 :: Int}

instance HasField "f3" R Int where
  getField (MkR a b) = a + b

-- If hls eval pluging is activated
-- >>> $(showFieldInstances ''R)
-- "[]"

I have zero knowledge of template haskell. Is there any way to do this?

Thanks in advance

EDIT

I have an ilumination that reifyInstances only works for "fully applied" type classes, so I modify:

getFieldIntances :: Name -> Q [InstanceDec]
getFieldIntances name = reifyInstances ''HasField  [ VarT ( mkName "a") , ConT name, VarT ( mkName "b")]

-- ...

-- >>> $(showFieldInstances ''R)
-- "[InstanceD Nothing [] (AppT (AppT (AppT (ConT GHC.Records.HasField) (LitT (StrTyLit \"f3\"))) (ConT Main.R)) (ConT GHC.Types.Int)) []]"

Which kind of works, but only for explicit instances (virtual fields). can I get ghc generated ones?

lsmor
  • 4,698
  • 17
  • 38
  • 3
    Looking up what instances there are is generally a pretty shaky thing to do (due to the open-world assumption). And `HasField` in particular is a bit of a semi-magic class. I think this is just not something you should attempt. – leftaroundabout May 31 '23 at 08:39

1 Answers1

4

You need :info!:

> :info! R
type R :: *
data R = MkR {f1 :: Int, f2 :: Int}
    -- Defined at test.hs:4:1
instance [safe] HasField "f3" R Int -- Defined at test.hs:6:10

The manual describes the difference:

:info[!] ⟨name⟩
...
For types and classes, GHCi also summarises instances that mention them. To avoid showing irrelevant information, an instance is shown only if (a) its head mentions ⟨name⟩, and (b) all the other things mentioned in the instance are in scope (either qualified or otherwise) as a result of a :load or :module commands.

The command :info! works in a similar fashion but it removes restriction (b), showing all instances that are in scope and mention ⟨name⟩ in their head.

Okay, so what's not in scope? This one is a bit tricky! Thanks to HTNW in the comments for the explanation. Check this out:

> :i HasField
type HasField :: forall k. k -> * -> * -> Constraint

Despite the fact that you generally call HasField with three arguments, it actually has four. In HasField "f3" R Int, there is an implicit argument Symbol naming the kind of "f3" (this is k in the declaration above). If you bring Symbol into scope, then :i again works:

> import GHC.TypeLits (Symbol)
GHC.TypeLits> :i R
type R :: *
data R = MkR {f1 :: Int, f2 :: Int}
    -- Defined at test.hs:4:1
instance [safe] HasField "f3" R Int -- Defined at test.hs:6:10
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • 1
    `"f3"` *is* always in scope, from my testing. What is happening is that `HasField` has an invisible argument; it is of kind `forall k. k -> Type -> Type -> Constraint`. So `instance HasField "f3" R Int` has `Symbol` as an invisible part of the instance head. (The instance is for the tuple `(Symbol, "f3", R, Int)`, not just for `("f3", R, Int)`.) So `:info` doesn't show the instance unless `Symbol` is imported. If you make a `class F (s :: Symbol) (x :: Type)`, then make an instance of `F` for a data type `D`, then issue `:i D` from a context with `D` but without `Symbol`, you get the instance. – HTNW May 31 '23 at 17:55
  • @HTNW indeed. If you import `GHC.TypeLits` it shows the instance. – lsmor May 31 '23 at 19:27