我有一组包装器类型FilePath
(由于我使用的库的限制,它们基于提供的类型创建特定的存储)和一些我需要从这些文件路径获取的记录
newtype SourceFilepath = SourceFilepath String deriving (Show)
newtype HeaderFilepath = HeaderFilepath String deriving (Show)
-- ..many more wrappers
data Source =
Source {..}
data Header =
Header {..}
data Metadata =
Metadata {..}
-- .. many more record types
我想创建一个通用函数loadSource
,该函数接受某些类型(实际上仅是文件路径包装器),并根据提供的类型产生另一种特定类型的值(Source
,Header
,{ {1}}等。伪代码:
Metadata
此功能无法使用,我遇到多个loadSource :: a -> Compiler b
loadSource (SourceFilepath path) = subload path
loadSource (HeaderFilepath path) = subload path
-- .. other cases for other types
--
-- `a` can be filepath wrappers
-- different `a` can lead to the same `b` sometimes
和a’ is a rigid type variable bound by the type signature
错误。
所以我没有这样的多个功能(代码正常工作):
rigid b..
我该如何实现?
答案 0 :(得分:2)
有多种方法可以实现这一目标,尽管正如@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
答案 1 :(得分:1)
也只需将包装器参数化:
newtype WrappedFilePath a = WrappedFilePath FilePath
loadSource :: FromJSON a => WrappedFilePath a -> Compiler a
loadSource (WrappedFilePath p) = subload fp
如果愿意,您可以重复使用Tagged
而不是创建新的WrappedFilePath
。