如何在 Haskell 中将返回类型多态性与参数多态性相结合?



>我有一组包装器类型FilePaths(由于我使用的库的限制,它们根据提供的类型创建特定的存储)和我需要从这些文件路径获取的几条记录。

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,并根据提供的类型生成另一个特定类型的值(SourceHeaderMetadata等)。伪代码:

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 signaturerigid b..错误。

所以我没有这样的多个函数(代码工作正常):

subload :: FromJSON b => FilePath -> Compiler b
subload path = <already implemented operational logic>
loadHeader :: HeaderFilepath -> Comiler Header
loadHeader (HeaderPath path) = subload path
loadMetadata :: MetadataFilepath -> Comiler Metadata
loadMetadata (MetadataFilepath path) = subload path
-- .. many more similar functions

我怎样才能做到这一点?

有几种方法可以实现这一点,尽管正如@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

只需将包装器也参数化:

newtype WrappedFilePath a = WrappedFilePath FilePath
loadSource :: FromJSON a => WrappedFilePath a -> Compiler a
loadSource (WrappedFilePath p) = subload fp

如果您愿意,可以重复使用Tagged而不是创建新WrappedFilePath

最新更新