如何显式实例化/专门化多态Haskell函数?

时间:2014-07-12 02:02:56

标签: haskell polymorphism

我想知道是否可以在Haskell中显式实例化/专门化多态函数?我的意思是,想象一下我的功能如下:

parseFile :: FromJSON a => FilePath -> IO Either String a

它尝试解析文件内容的结构将取决于a的类型。现在,我知道可以通过注释指定a

parseFile myPath :: IO Either String MyType

我想知道的是,是否可以更明确地专门化parseFile,例如(specialise parseFile MyType)之类的内容可以将其变为parseFile :: FilePath -> IO Either String MyType

我问的原因是注释方法可能会因较大的功能而变得笨拙。例如,假设parseFilefoo调用,bar调用bar:: FromJSON a => IO (([Int],String), (Int, String, Int), a, (Double, [String])) 的返回值具有类似

的复杂类型
a

这意味着,如果我想将MyType称为:: IO (([Int],String), (Int, String, Int), MyType, (Double, [String])) ,我必须使用

注释来电
res1 <- bar inputA :: IO (([Int],String), (Int, String, Int), MyType, (Double, [String]))
res2 <- bar inputB :: IO (([Int],String), (Int, String, Int), OtherType, (Double, [String]))
res3 <- bar inputC :: IO (([Int],String), (Int, String, Int), YetAnotherType, (Double, [String]))

如果我想多次调用bar来处理不同的类型,我最后多次写这个注释,这似乎是不必要的重复。

bar inputA

有没有办法避免这种情况?我知道可以绑定MyType的结果并在期望a的函数中使用它,允许类型引擎推断出所讨论的MyType是{ {1}}无需明确注释。这似乎牺牲了类型的安全性,好像我在一个期望bar inputB的函数中意外地使用了上面的OtherTypeMyType)的结果,例如,类型系统不会不要抱怨,在尝试将inputB解析为MyType时,程序会在运行时失败,因为inputB包含OtherType,而不是MyType

2 个答案:

答案 0 :(得分:1)

首先,小修正,类型应为

parseFile :: FromJSON a => FilePath -> IO (Either String a)

括号是重要且必要的


有几种方法可以解决这个问题。例如,如果你有一个功能

useMyType :: MyType -> IO ()
useMyType = undefined

然后您使用parseFile作为

main = do
    result <- parseFile "data.json"
    case result of
        Left err -> putStrLn err
        Right mt -> useMyType mt

不需要额外的类型注释,GHC可以使用mt推断出useMyType的类型。


另一种方法是简单地将其分配给具体类型的名称:

parseMyTypeFromFile :: FilePath -> IO (Either String MyType)
parseMyTypeFromFile = parseFile

main = do
    result <- parseMyTypeFromFile "data.json"
    case result of
        Left err -> putStrLn err
        Right mt -> useMyType mt

在任何地方使用parseMyTypeFromFile都不需要明确的注释。这与指定read的类型的通用做法相同:

readInt :: String -> Int
readInt = read

为了解决bar问题,如果你有一个复杂的类型,我至少会建议为它创建一个别名,如果不是它自己的数据类型,可能还有记录字段和诸如此类的东西。类似于

的东西
data BarType a = (([Int], String), (Int, String, Int), a, (Double, [String]))

然后您可以将bar写为

bar :: FromJSON a => InputType -> IO (BarType a)
bar input = implementation details

使bar更好阅读。那你就可以做到

res1 <- bar inputA :: IO (BarType MyType)
res2 <- bar inputB :: IO (BarType OtherType)
res3 <- bar inputC :: IO (BarType YetAnotherType)

我个人认为这个非常明确和惯用的Haskell。它不仅可以立即读取并清楚您正在做什么,而且通过使用名称来引用复杂类型,您可以最大限度地减少拼写错误的可能性,利用IDE自动完成功能,并且可以将文档放在类型本身上以便让其他人使用(以及你未来的自我)知道所有这些领域的含义。

答案 1 :(得分:1)

您不能在其他地方提供多态函数,并将明确的注释赋予具有相同名称的更受限制的版本。但你可以做类似的事情:

  parseFileOfMyType :: FilePath -> IO Either String MyType
  parseFileOfMyType = parseFile

各种库中令人惊讶的有用函数数量类似于类型特定的不起眼函数别名,如id。无论如何,您应该能够使用这种技术制作这些示例的类型约束版本。

详细问题的另一个解决方案是创建类型别名:

type MyInputParse a = IO (([Int],String), (Int, String, Int), a, (Double, [String]))

res1 <- bar inputA :: MyInputParse MyType
res2 <- bar inputB :: MyInputParse OtherType
res3 <- bar inputC :: MyInputParse YetAnotherType

在不久的将来,GHC可能会获得一种提供部分类型签名的机制,这将允许您在类型签名中留下某种漏洞,推理将在您创建部分时填写对具体的兴趣。但它还没有。