Haskell推断出太严格的类型

时间:2015-09-18 22:57:23

标签: haskell type-inference

免责声明:我刚刚开始学习Haskell,我不确定“严格”是否正确。

我试图缩小我的问题,但我找不到问题,所以这是我的代码无法编译

module Json where
import Data.List (intersperse)

data JNode = 
    JObject [(String, JNode)]
  | JArray  [JNode]
  | JString String
  | JNumber Double
  | JBool   Bool
  | JNull

instance Show JNode where
  show = show_node 0 where
    glue = foldl (++) ""
    show_tabs n              = glue $ take n $ repeat "  "
    show_list n              = glue . intersperse ",\n" . map (show_pair (n + 1))
    show_sect n l r xs       = glue ["\n", tabs, l, "\n", show_list n xs, "\n", tabs, r] where tabs = show_tabs n
    -- show_pair :: (Show a) => Int -> (a, JNode) -> String -- works when uncommented
    show_pair n (name,  val) = glue [show_tabs n, show name, " : ", show_node n val]
    show_node n (JObject xs) = show_sect n "{" "}" xs
    show_node n (JArray  xs) = show_sect n "[" "]" $ zip [0..] xs
    show_node n (JString x ) = show x
    show_node n (JNumber x ) = show x
    show_node n (JBool   x ) = show x
    show_node n (JNull     ) = "null"

错误是:

Prelude> :l scripts\json.hs
[1 of 1] Compiling Json             ( scripts\json.hs, interpreted )

scripts\json.hs:21:59:
    No instance for (Enum String)
      arising from the arithmetic sequence `0 .. '
    In the first argument of `zip', namely `([0 .. ])'
    In the second argument of `($)', namely `zip ([0 .. ]) xs'
    In the expression: show_sect n "[" "]" $ zip ([0 .. ]) xs

scripts\json.hs:21:60:
    No instance for (Num String) arising from the literal `0'
    In the expression: 0
    In the first argument of `zip', namely `[0 .. ]'
    In the second argument of `($)', namely `zip [0 .. ] xs'
Failed, modules loaded: none.

查看带注释的代码行。显然,如果没有类型声明,则需要我传递String而不是Show a。有趣的是,当我甚至不使用它时,它仍然需要nameString,例如用此替换show_pair实现时:

show_pair n (name,  val) = show_node n val

有人可以向我解释它为什么会这样运作吗?

我的代码的 简化版,如果有人想要改进答案,则会遇到同样的问题:

data TFoo = 
    FooStr (String, TFoo)
  | FooNum (Int,    TFoo)

-- show_pair :: (a, TFoo) -> String
show_pair (_,    val) = show_node val
show_node (FooStr  x) = show_pair x
show_node (FooNum  x) = show_pair x

2 个答案:

答案 0 :(得分:9)

tl; dr :当您希望某些内容具有多态性时,请始终使用显式签名。

System F(这是Haskell的类型系统所基于的)中,用于多态的每个类型变量都需要通过类型级别lambda / for-all(∀)显式地量化为范围。所以实际上你需要

show_pair :: ∀ a. (Show a) => Int -> (a, JNode) -> String

可以说Haskell也应该要求这样做,但事实并非如此。它只是量化您在显式签名中提到的任何变量,因此您只需编写

show_pair :: (Show a) => Int -> (a, JNode) -> String

此外,它尝试在没有签名的情况下将尽可能多的类型变量引入顶级绑定

但是,它不会自动将任何类型变量引入 local 绑定。由于编译器确切地知道show_pair的确切使用位置,因此它具有至少一个您需要具有的类型实例化的完整上下文。它假设您只需要一个实例。有了它,它试图推断单态 show_pair的某种类型,并失败。只有通过添加显式签名才能强制编译器考虑多态签名。 正如评论中所述,这实际上并不正确,因为自GHC-7.0以来,有一个叫做{{3即使没有签名,它也会使本地绑定变为多态。我不知道这是默认开启的。 - 即使可能省略签名,多态函数仍然应该更好地使它们成为IMO。

如果您尚未在更高的范围内引入该变量,并使用-XScopedTypeVariables扩展名。

不幸的是,let generalisation使得这非常难以依赖。

答案 1 :(得分:0)

好吧,我认为您希望[0..]被视为[Int],而事实似乎并非如此。这可能是因为show_node JObject传递[(String, JNode)],因为与JArray相同的参数会将[(SOMETHING_UNKNOWN, JNode)]传递给show_sect

尝试强制使用与数组元素一起压缩的表达式[0..]上的类型:zip ([0..] :: [Int]) xs。我确信问题是推断的show_sect类型。

我强烈建议提供一些类型的注释,我认为这是一个很好的做法。我对Haskell中的类型推断并不太熟悉。