Generically iterating over accessors of a product type

89 Views Asked by At

I've written the following function using generics-sop. What it does, is given a value of a product type, goes through all it's members, applies a function to all those members, and spits out a list of the results:

import Generics.SOP qualified as SOP
import Generics.SOP hiding (Generic)

productToList :: forall c a t xs. (
  IsProductType a xs, 
  SOP.Generic a, 
  ProductCode a ~ xs, 
  All c xs
  ) 
  => (forall b. c b => b -> t) -> a -> [t]
productToList f = hcollapse . hcmap (Proxy :: Proxy c) (mapIK f) . productTypeFrom

Here's some example usage:

import Generics.SOP qualified as SOP
import Generics.SOP hiding (Generic)

data MyNums = MyNums { anInt :: Int, anInteger :: Integer, aFloat :: Float }
  deriving stock Generic
  deriving anyclass SOP.Generic

class (Eq a, Num a) => EqNum a 
instance (Eq a, Num a) => EqNum a 

c = MyNums { anInt = 0, anInteger = 5, aFloat = 7.2 }

f :: (Eq a, Num a) => a -> Bool
f x = x == 0

y :: [Bool]
y = genericProductToList @EqNum f c

This will output the list [True, False, False].

So far so good. But with some code I'm trying to make generic now, the signature I need is slightly different. Instead of:

productToList          :: ... => (forall b. c b => b -> t) -> a -> [t]

I want

productAccessorsToList :: ... => (forall b. c b => (a -> b) -> t) -> [t]

Basically instead of passing a value of a structure and iterating over the members, in this case don't have a value, but I want to iterate over the accessors functions of a type.

I've basically moved the a from outside the forall to inside the forall.

What is an implementation of productAccessorsToList?

1

There are 1 best solutions below

0
On

The fundamental functionality is contained in projections. Manipulate that.

projectionsToList ::
  forall c a t xs.
  ( IsProductType a xs
  , SOP.Generic a
  , ProductCode a ~ xs
  , All c xs
  )
 => (forall b. c b => (a -> b) -> t)
 -> [t]
projectionsToList f =
    hcollapse @_ @_ @NP @xs
  $ hcmap (Proxy @c) adjust (projections @_ @I)
  where
    adjust :: forall x. c x => (K (NP I xs) -.-> I) x -> K t x
    adjust p = K $ f $ unI . apFn p . K . productTypeFrom

You can recover productToList from this, but I don't believe it works the other way around

productToList' ::
   forall c a t xs.
   ( IsProductType a xs
   , SOP.Generic a
   , ProductCode a ~ xs
   , All c xs
   ) 
  => (forall b. c b => b -> t)
  -> a -> [t]
productToList' f x = projectionsToList @c @a (f . ($ x))
-- would probably be slower than productToList