[Haskell-cafe] Is there a way to make this code compose generic ?

Li-yao Xia lysxia at gmail.com
Mon Apr 7 19:19:12 UTC 2025


Hi Frederic,

Below is a generic implementation of your class based on your example, 
that should get you started. Two changes are worth calling out:

- I assumed that the binary operation `combine'Shape` is associative. It 
takes a bit more effort to associate the exact same as `foldl1`.

- To avoid duplicating code between `DataSourcePath` and 
`DataSourceAcq`, I merged them as a single type indexed by a type-level 
flag.

For more information, several tutorials on Haskell generics are findable 
on search engines.

Cheers,
Li-yao

---

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module G where

import GHC.Generics
import Data.Kind (Type)

-- * Interface

data DataSourceShape
   = DummyDSS Int

combine'Shape :: DataSourceShape -> DataSourceShape -> DataSourceShape
combine'Shape (DummyDSS x) (DummyDSS y) = DummyDSS (x + y)

data DSKind = Path | Acq

class DataSource a where
   data DataSourceT (k :: DSKind) a :: Type

   ds'Shape :: Monad m => DataSourceT Acq a -> m DataSourceShape
   withDataSourceP :: String -> DataSourceT Path a -> (DataSourceT Acq a 
-> m r) -> m r

-- | Generic 'ds'Shape'
generic'ds'Shape :: (Monad m, Generic (DataSourceT Acq a), 
GDataSourceAcq (Rep (DataSourceT Acq a))) => DataSourceT Acq a -> m 
DataSourceShape
generic'ds'Shape = g'ds'Shape . from

-- | Generic 'withDataSourceP'
generic'withDataSourceP ::
   (Generic (DataSourceT Path a), Generic (DataSourceT Acq a), 
GDataSourcePath (Rep (DataSourceT Path a)) (Rep (DataSourceT Acq a))) =>
   String -> DataSourceT Path a -> (DataSourceT Acq a -> m r) -> m r
generic'withDataSourceP file src gg = g'withDataSourceP file (from src) 
(gg . to)

-- ** Base instance

type family DataSourceBase (k :: DSKind) :: Type where
   DataSourceBase Acq = String
   DataSourceBase Path = [String]

data BaseData
instance DataSource BaseData where
   newtype DataSourceT k BaseData = DataSource'BaseData (DataSourceBase k)
   ds'Shape _ = pure (DummyDSS 1)
   withDataSourceP _ _ k = k (DataSource'BaseData "source")

-- * Generic example usage

data ExampleData
instance DataSource ExampleData where
   data DataSourceT k ExampleData = DataSource'ExampleData
     (DataSourceT k BaseData)
     (DataSourceT k BaseData)
     (DataSourceT k BaseData)
     (DataSourceT k BaseData)
     (DataSourceT k BaseData)
     deriving Generic
   ds'Shape = generic'ds'Shape
   withDataSourceP = generic'withDataSourceP

-- * Generic implementation

class GDataSourceAcq dataAcq where
   g'ds'Shape :: Monad m => dataAcq x -> m DataSourceShape

class GDataSourcePath dataPath dataAcq where
   g'withDataSourceP :: String -> dataPath x -> (dataAcq x -> m r) -> m r

instance GDataSourceAcq f => GDataSourceAcq (M1 i c f) where
   g'ds'Shape (M1 f) = g'ds'Shape f

instance GDataSourcePath f g => GDataSourcePath (M1 i c f) (M1 i c' g) where
   g'withDataSourceP f (M1 d) gg = g'withDataSourceP f d (gg . M1)

instance (GDataSourceAcq f, GDataSourceAcq f') => GDataSourceAcq (f :*: 
f') where
   g'ds'Shape (f :*: f') = liftA2 combine'Shape (g'ds'Shape f) 
(g'ds'Shape f')

instance (GDataSourcePath f g, GDataSourcePath f' g') => GDataSourcePath 
(f :*: f') (g :*: g') where
   g'withDataSourceP file (f :*: f') gg =
     g'withDataSourceP file f $ \g ->
     g'withDataSourceP file f' $ \g' ->
       gg (g :*: g')

instance DataSource a => GDataSourceAcq (K1 i (DataSourceT Acq a)) where
   g'ds'Shape (K1 acq) = ds'Shape acq

instance DataSource a => GDataSourcePath (K1 i (DataSourceT Path a)) (K1 
i (DataSourceT Acq a)) where
   g'withDataSourceP file (K1 acq) gg =
     withDataSourceP file acq $ \dat ->
       gg (K1 dat)



More information about the Haskell-Cafe mailing list