如何在Haskell中将返回类型多态性与参数多态性结合起来?
我有一组包装类型如何在Haskell中将返回类型多态性与参数多态性结合起来?,haskell,polymorphism,parametric-polymorphism,Haskell,Polymorphism,Parametric Polymorphism,我有一组包装类型FilePaths(由于我使用的库的限制,它根据提供的类型创建特定的存储)和我需要从这些文件路径获取的两条记录 newtype SourceFilepath=SourceFilepath字符串派生(显示) newtype HeaderFilepath=HeaderFilepath字符串派生(显示) --还有很多包装纸 数据源= 来源{..} 数据头= 标题{..} 数据元数据= 元数据{..} -- .. 更多的记录类型 我想创建一个通用函数loadSource,该函数接受某些
FilePath
s(由于我使用的库的限制,它根据提供的类型创建特定的存储)和我需要从这些文件路径获取的两条记录
newtype SourceFilepath=SourceFilepath字符串派生(显示)
newtype HeaderFilepath=HeaderFilepath字符串派生(显示)
--还有很多包装纸
数据源=
来源{..}
数据头=
标题{..}
数据元数据=
元数据{..}
-- .. 更多的记录类型
我想创建一个通用函数loadSource
,该函数接受某些类型(实际上只接受文件路径包装器),并基于提供的类型生成另一个特定类型的值(Source
,Header
,元数据
,等等)。伪代码:
loadSource::a->编译器b
loadSource(SourceFilepath路径)=子加载路径
加载源(HeaderFilepath路径)=子加载路径
-- .. 其他类型的其他案例
--
--`a`可以是文件路径包装器
--不同的'a'有时会导致相同的'b'
此函数不可操作。我得到多个a'是一个刚性类型变量,受类型签名
和刚性b..
错误的约束
所以我没有这样的多个函数(代码正常工作):
subload::FromJSON b=>FilePath->Compiler b
子加载路径=
loadHeader::HeaderFilepath->Comiler标头
loadHeader(HeaderPath路径)=子加载路径
loadMetadata::MetadataFilepath->Comiler元数据
loadMetadata(MetadataFilepath路径)=子加载路径
-- .. 更多类似的功能
我怎样才能做到这一点呢?也要将包装参数化:
newtype WrappedFilePath a = WrappedFilePath FilePath
loadSource :: FromJSON a => WrappedFilePath a -> Compiler a
loadSource (WrappedFilePath p) = subload fp
如果愿意,您可以重用而不是创建新的
WrappedFilePath
。有几种方法可以实现这一点,尽管正如@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
以及标记解决方案的完整来源:
{-# 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
我试过了,编译器子系统认为它是同一类型的文件,并尝试应用无法工作的函数。这就是为什么我需要单独的
头文件路径
和源文件路径
@FilipvanHoft我不理解你的第一条评论。您的伪代码已经为loadSource
中的两种模式调用了相同的subload
函数;这是否反映了您实际希望发生的事情?如果没有,那么也许您可以提供一些您希望发生的事情的详细信息?loadSource
不可操作,我想这就是我可能使用它的方式。@FilipvanHoft好的。我不认为任何人能进一步帮助你,直到你说出更多你想要的行为。谢谢!这很有帮助,类型类和相关的类型族涵盖了我的案例。