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

时间:2019-06-13 15:00:57

标签: haskell polymorphism parametric-polymorphism

我有一组包装器类型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,该函数接受某些类型(实际上仅是文件路径包装器),并根据提供的类型产生另一种特定类型的值(SourceHeader,{ {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..

我该如何实现?

2 个答案:

答案 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