如何在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好的。我不认为任何人能进一步帮助你,直到你说出更多你想要的行为。谢谢!这很有帮助,类型类和相关的类型族涵盖了我的案例。