【问题标题】:Result type of a polyvariadic function in haskellhaskell中多变量函数的结果类型
【发布时间】:2020-05-31 10:10:38
【问题描述】:

在 Haskell 中研究多变量函数时,我偶然发现了以下 SO 问题:

How to create a polyvariadic haskell function?

Haskell, polyvariadic function and type inference

我想我会通过实现一个函数来尝试一下,该函数接受可变数量的字符串并将它们连接/合并成一个字符串:

{-# LANGUAGE FlexibleInstances #-}

class MergeStrings r where
    merge :: String -> r
instance MergeStrings String where
    merge = id
instance (MergeStrings r) => MergeStrings (String -> r) where
    merge acc = merge . (acc ++)

到目前为止,如果我使用至少一个字符串参数调用合并并且提供最终类型,则此方法有效。

foo :: String
foo = merge "a" "b" "c"

省略最终类型会导致错误,即编译以下内容

bar = merge "a" "b" "c"

结果

test.hs:12:7: error:
    • Ambiguous type variable ‘t0’ arising from a use of ‘merge’
      prevents the constraint ‘(MergeStrings t0)’ from being solved.
      Relevant bindings include bar :: t0 (bound at test.hs:12:1)
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instances exist:
        instance MergeStrings r => MergeStrings (String -> r)
          -- Defined at test.hs:6:10
        instance MergeStrings String -- Defined at test.hs:4:10
    • In the expression: merge "a" "b" "c"
      In an equation for ‘bar’: bar = merge "a" "b" "c"
   |
12 | bar = merge "a" "b" "c"
   |

错误信息非常有意义,因为我可以很容易地想出例如

bar :: String -> String
bar = merge "a" "b" "c"

baz = bar "d"

不是将bar 渲染成单个字符串,而是渲染成一个接受并返回一个字符串的函数。

有没有办法告诉 Haskell 结果类型必须是 String 类型?例如,Text.Printf.printf "hello world" 在没有明确定义的情况下计算为 String

【问题讨论】:

  • 澄清一下:您似乎是在说,当merge a b c etc…的类型无法推断时,您希望GHC推断String的类型,否则使用推断的类型。这种解释正确吗?我想不出任何办法来做到这一点,但我也不能肯定地说这是不可能的。
  • 顺便说一句:看起来您的printf 示例仅有效,因为PrintfType 具有IO 的实例;在 GHCi 中进行测试时,任何可以专门用于 IO 的类型都将自动运行。如果我定义instance (a ~ ()) => MergeStrings (IO a) where merge = putStrLnbar = merge "a" "b" "c" 在 GHCi 中也适用于我。而且,在 GHCi 之外,printf "hello world" 会导致模棱两可的类型变量错误,与bar = merge "a" "b" "c" 完全相同。
  • 为了澄清:我不希望 GHC 推断结果类型,而是在合并的定义端定义结果类型,这样在使用合并时我不必提供结果类型。你对 GHCi 和 IO 的观察真的很有趣,我也会和他们一起玩!似乎@max-taldykin 搞定了

标签: haskell polyvariadic


【解决方案1】:

printf 在 GHCi 中由于 type defaulting 而无需类型注释即可工作。允许您在不指定具体类型的情况下评估 show $ 1 + 2 的相同机制。

GHCi tries to evaluate expressions of type IO a,所以你只需要为MergeStrings添加适当的实例:

instance (a ~ ()) => MergeStrings (IO a) where
    merge = putStrLn

【讨论】:

  • 天哪,你是对的 :) 我没想到它会是 GHCi 的东西。仅使用 GHC,然后 foo = printf "hello world" 不使用类型注释也会导致编译错误。感谢您解开这个谜团!
【解决方案2】:

Brad (in a comment) 和 Max 说 printf "…" …IO ( ) 的默认值是没有错的,这是它在 ghci 中没有类型注释的原因。但这不是故事的结局。我们可以做一些事情来使您对bar 的定义生效。

首先,我应该提到«单态限制»——我们在 Haskell 中的一个晦涩且不直观的类型推断规则。无论出于何种原因,Haskell 的设计者决定,没有类型签名的顶级定义在其推断类型中不应该有多态变量——也就是说,是单态的。 bar 是多态的,所以你可以看到它会受到影响。

某些类型类(尤其是数字)具有默认规则,允许您在没有类型签名的情况下说出 x = 13 并让它推断出 x :: Integer — 或其他您设置为默认值的其他类型。类型默认仅适用于少数祝福类,因此您无法为自己的类使用它,并且没有指定的默认 GHC 无法决定选择哪种特定的单态类型。

但你可以做其他事情,除了默认,让类型检查器满意——要么:

现在bar 是多态的,可以按照您的预期工作。见:

λ putStrLn bar
abc
λ putStrLn (bar "x")
abcx
λ putStrLn (bar "x" "y")
abcxy

您还可以使用默认设置使show bar 等表达式起作用。由于Show 是启用extended default rules 时可以默认使用的类之一,因此您可以在要使用show bar 的模块中发出default (String),它会按预期工作。

【讨论】:

  • 非常感谢您的详细解答!我根本不知道这种单态限制。就像你说的,添加一个显式的多态类型签名解决了我的问题,而且我感觉很好:) 所以我会继续这样做。
  • IOW 这回答了 OP 的问题“有没有办法告诉 Haskell 结果类型必须是字符串类型?”用“你不应该,保持它是多态的,然后调用上下文将确定每种情况下的类型”!。 :) 不错。
猜你喜欢
  • 2017-04-15
  • 2016-01-24
  • 2011-01-10
  • 1970-01-01
  • 1970-01-01
  • 2019-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多