【问题标题】:How to explicitly instantiate/specialise a polymorphic Haskell function?如何显式实例化/专门化多态 Haskell 函数?
【发布时间】:2014-07-12 02:02:55
【问题描述】:

我想知道是否可以在 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 调用,而bar 的返回值具有类似

:: FromJSON a => IO (([Int],String), (Int, String, Int), a, (Double, [String]))

这意味着如果我想用a 调用bar 作为MyType,我必须用

注释调用
:: IO (([Int],String), (Int, String, Int), MyType, (Double, [String]))

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

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 inputA 的结果并在需要MyType 的函数中使用它,从而允许类型引擎推断有问题的aMyType 而无需显式注释.然而,这似乎牺牲了类型安全,就好像我不小心在一个需要 MyType 的函数中使用了上述 bar inputB(一个 OtherType)的结果,例如,类型系统不会抱怨,而是当尝试将inputB 解析为MyType 时,程序会在运行时失败,因为inputB 包含OtherType,而不是MyType

【问题讨论】:

    标签: haskell polymorphism


    【解决方案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 可以通过与useMyType 一起使用来推断mt 的类型。


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

    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 自动完成功能,并且可以将文档放在类型本身上以供其他人使用(以及你未来的自己)知道所有这些领域的含义。

    【讨论】:

      【解决方案2】:

      您不能将在别处提供的多态函数和显式注释转换为具有相同名称的更受限制的版本。但是您可以执行以下操作:

        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 可能会获得一种提供部分类型签名的机制,这将让您在类型签名中留下某种漏洞,当您制作自己的部分时,推理将填补这些漏洞。重新对具体感兴趣。但它还没有。

      【讨论】:

        猜你喜欢
        • 2013-12-05
        • 1970-01-01
        • 2022-01-10
        • 2018-01-25
        • 1970-01-01
        • 2011-06-23
        • 2016-05-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多