我想知道是否可以在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
我问的原因是注释方法可能会因较大的功能而变得笨拙。例如,假设parseFile
由foo
调用,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
的函数中意外地使用了上面的OtherType
(MyType
)的结果,例如,类型系统不会不要抱怨,在尝试将inputB
解析为MyType
时,程序会在运行时失败,因为inputB
包含OtherType
,而不是MyType
。
答案 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可能会获得一种提供部分类型签名的机制,这将允许您在类型签名中留下某种漏洞,推理将在您创建部分时填写对具体的兴趣。但它还没有。