Haskell推断出太严格的类型

use*_*068 9 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"
Run Code Online (Sandbox Code Playgroud)

错误是:

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.
Run Code Online (Sandbox Code Playgroud)

看一下带注释的代码行.显然,当没有类型声明时,它需要我String而不是仅仅通过Show a.有趣的是,它仍然需要name成为String我甚至不使用它的时候,例如当用show_pair这个替换实现时:

show_pair n (name,  val) = show_node n val
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么它的工作方式如何吗?


我的代码的简化版本具有相同的问题,以防任何人想要改进答案:

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
Run Code Online (Sandbox Code Playgroud)

lef*_*out 9

tl; dr:当你想要多态的东西时,总是使用显式签名.


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

show_pair :: ? a. (Show a) => Int -> (a, JNode) -> String
Run Code Online (Sandbox Code Playgroud)

可以说Haskell也应该要求这样做,但事实并非如此.它只是量化你在显式签名†中提到的任何变量,所以你可以简单地写

show_pair :: (Show a) => Int -> (a, JNode) -> String
Run Code Online (Sandbox Code Playgroud)

此外,它尝试将尽可能多的类型变量引入顶级绑定而无需签名.

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


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

不幸的是,单态限制使得这很难依赖.

  • 为什么不让一般化抓住这个? (2认同)
  • @dfeuer我想这是因为相互递归.泛化发生在整个相互递归的定义组中,但那时为时已晚. (2认同)

jak*_*iel 0

好吧,我想你预计[0..]会被考虑一段[Int]时间,但情况似乎并非如此。这可能是因为show_nodeforJObject传递的参数与传递到[(String, JNode)]的参数相同。JArray[(SOMETHING_UNKNOWN, JNode)]show_sect

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

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