haskell 中多元函数的结果类型

Result type of a polyvariadic function in haskell

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

How to create a polyvariadic haskell function?

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

{-# 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 ++)

如果我用至少一个字符串参数调用 merge 并且我提供最终类型,那么到目前为止这有效。

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 不是单个字符串,而是一个接受 returns 一个字符串的函数。

有没有办法告诉Haskell结果类型必须是String类型?例如,Text.Printf.printf "hello world" 的计算结果为类型 String 而无需显式定义。

由于 GHCi 中的 type defaulting

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

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

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

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

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

有些类型 classes (特别是数字)默认规则 允许你说 x = 13没有类型签名,并让它推断出 x :: Integer — 或您设置为默认值的任何其他类型。类型默认仅适用于少数幸运的 classes,因此您不能为自己的 class 使用它,并且没有指定的默认 GHC 无法决定选择哪种特定的单态类型。

但是除了默认设置之外,您还可以做其他事情来让类型检查器满意 — 要么:

现在 bar 是多态的并且可以像您期望的那样工作。参见:

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

您还可以使用默认设置来使 show bar 等表达式起作用。由于 Show 是启用 extended default rules 时可以默认的 classes 之一,您可以在要使用 show bar 的模块中发出 default (String) 和它会像您期望的那样工作。