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

Li-yao Xia lysxia at gmail.com
Wed Apr 9 18:16:44 UTC 2025


The DataSourceT type is already HKDified. You can make it the parameter 
of the class directly:

class DataSource d where
   ds'Shape :: MonadSafe m => d Acq -> m DataSourceShape
   withDataSourceP :: MonadSafe m => ... -> d Path -> (d Acq -> m r) -> m r

data Sample k = MkSample
   (DataSourceDouble k) -- define a wrapper for each base type which 
will be the new argument for the corresponding DataSource instance
   (Degree k)
   -- (...)

instance DataSource Sample where
   -- (...)

For your issue with sums, it doesn't seem right to encode alternative 
"data paths" as extra constructors. Correct me if you had a different 
idea in mind.

To start, given the DataSource method:

withDataSourceP :: MonadSafe m => ... -> d Path -> (d Acq -> m r) -> m r

you can implement:

withDataSourcesP :: (DataSource d, MonadSafe m) => ... -> [d Path] -> (d 
Acq -> m r) -> m r

by trying `withDataSourceP` with each element in the list.

Now I'm guessing that the reason you wanted an `Or` constructor was so 
that you could list alternatives to populate individual components of 
your struct. For example, maybe there are N possible sources for some 
data alpha, and M possible sources for some data beta, and you don't 
want to turn that into a flat list of N*M ways to get (alpha, beta).

The goal is for `Sample k` to look like this when `k = Path`:

data Sample Path = MkSample
   [DataSourceDouble Path]
   [Degree Path]
   -- (...)

but stay like this when `k = Acq`:

data Sample Acq = MkSample
   (DataSourceDouble Acq)
   (Degree Acq)

That is possible by creating a field wrapper parameterized by `k`:

data Sample k = MkSample
   (Wrap k (DataSourceDouble k))
   (Wrap k (Degree k))

So that Wrap Path t = [t] and Wrap Acq t = t.

type family Wrap (k :: DSKind) t where
   Wrap Path t = [t]
   Wrap Acq t = t

Below is a compilable example, modified from my previous email with the 
changes described above. On the generic side, the main change is that 
some (DataSourceT Path a) become [a Path] (with the list type!) and some 
calls to withDataSourceP become withDataSourcesP that I introduced above.

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)

-- simplified variants of catch and throw for the sake of example
class MonadCatch m where
   throw_ :: m a
   catch_ :: m a -> m a -> m a

data DSKind = Acq | Path
type family DSWrap (k :: DSKind) (t :: Type) :: Type where
   DSWrap Acq t = t
   DSWrap Path t = [t]

type DSWrap_ f k = DSWrap k (f k)

class DataSource d where
   ds'Shape :: Monad m => d Acq -> m DataSourceShape
   withDataSourceP :: MonadCatch m => String -> d Path -> (d Acq -> m r) 
-> m r

withDataSourcesP :: (DataSource d, MonadCatch m) => String -> [d Path] 
-> (d Acq -> m r) -> m r
withDataSourcesP file [] _ = throw_
withDataSourcesP file (s : ss) k = withDataSourceP file s k `catch_` 
withDataSourcesP file ss k

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

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

-- ** Base instance

data family BaseData (k :: DSKind)
newtype instance BaseData Acq = BaseDataAcq String
newtype instance BaseData Path = BaseDataPath [String]

instance DataSource BaseData where
   ds'Shape _ = pure (DummyDSS 1)
   withDataSourceP _ _ k = k (BaseDataAcq "source")

-- * Generic example usage

data ExampleData (k :: DSKind)
   = ExampleData
       (DSWrap_ BaseData k)
       (DSWrap_ BaseData k)
       (DSWrap_ BaseData k)
       (DSWrap_ BaseData k)
       (DSWrap_ BaseData k)
   deriving Generic

instance DataSource ExampleData where
   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 :: MonadCatch m => 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 (a Acq)) where
   g'ds'Shape (K1 acq) = ds'Shape acq

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



More information about the Haskell-Cafe mailing list