有几种方法可以实现这一点,但正如 @DanielWagner 所说,如果没有关于您想要实现的目标的更多详细信息,很难判断哪种方法最适合您。
最简单的可能是使用具有关联类型族的类型类(或具有功能依赖的多参数类型类)将文件路径包装器的类型映射到编译器子类型。类型族方法如下所示:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
class Loadable a where
filepath :: a -> String
type Load a
带有样板实例,例如:
instance Loadable SourceFilepath where
filepath (SourceFilepath pth) = pth
type Load SourceFilepath = Source
instance Loadable HeaderFilepath where
filepath (HeaderFilepath pth) = pth
type Load HeaderFilepath = Header
instance Loadable MetadataFilepath where
filepath (MetadataFilepath pth) = pth
type Load MetadataFilepath = Metadata
请注意,这里将两个文件路径包装器映射到相同的编译器子类型没有问题(例如,type Load HeaderFilepath = Source 可以正常工作)。
给定:
subload :: FromJSON b => FilePath -> Compiler b
subload = ...
loadSource的定义是:
loadSource :: (Loadable a, FromJSON (Load a)) => a -> Compiler (Load a)
loadSource = subload . filepath
之后:
> :t loadSource (SourceFilepath "bob")
loadSource (SourceFilepath "bob") :: Compiler Source
> :t loadSource (MetadataFilepath "alice")
loadSource (MetadataFilepath "alice") :: Compiler Metadata
您可以通过参数化包装器来大大减少样板文件,并且 - 就像 @DanielWagner - 我不理解您关于编译器将它们视为相同类型的文件的评论,因此您需要向我们展示发生了什么当你尝试的时候错了。
无论如何,我的原始类型族解决方案的完整来源:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}
import Data.Aeson
import GHC.Generics
newtype SourceFilepath = SourceFilepath String deriving (Show)
newtype HeaderFilepath = HeaderFilepath String deriving (Show)
newtype MetadataFilepath = MetadataFilepath String deriving (Show)
data Source = Source deriving (Generic)
data Header = Header deriving (Generic)
data Metadata = Metadata deriving (Generic)
instance FromJSON Source
instance FromJSON Header
instance FromJSON Metadata
data Compiler b = Compiler
subload :: FromJSON b => FilePath -> Compiler b
subload = undefined
class Loadable a where
filepath :: a -> String
type Load a
instance Loadable SourceFilepath where
filepath (SourceFilepath pth) = pth
type Load SourceFilepath = Source
instance Loadable HeaderFilepath where
filepath (HeaderFilepath pth) = pth
type Load HeaderFilepath = Header
instance Loadable MetadataFilepath where
filepath (MetadataFilepath pth) = pth
type Load MetadataFilepath = Metadata
loadSource :: (Loadable a, FromJSON (Load a)) => a -> Compiler (Load a)
loadSource = subload . filepath
以及标记解决方案的完整来源:
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}
import Data.Aeson
import GHC.Generics
newtype TypedFilePath a = TypedFilePath FilePath deriving (Show)
data Source = Source deriving (Generic)
data Header = Header deriving (Generic)
data Metadata = Metadata deriving (Generic)
instance FromJSON Source
instance FromJSON Header
instance FromJSON Metadata
data Compiler b = Compiler
subload :: FromJSON b => FilePath -> Compiler b
subload = undefined
type family Load a where
Load Source = Source
Load Header = Header
Load Metadata = Metadata
loadSource :: FromJSON (Load a) => TypedFilePath a -> Compiler (Load a)
loadSource (TypedFilePath fn) = subload fn