【发布时间】:2015-09-18 22:57:23
【问题描述】:
免责声明:我才刚开始学习 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。有趣的是,当我什至不使用它时,它仍然需要 name 成为 String,例如当用这个替换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
【问题讨论】:
-
snake_case在 Haskell 中不常用,camelCase是最遵循的命名方式。 -
我强烈建议将所有这些本地函数分解到顶层并给每个函数一个类型签名。你不需要从你的模块中导出它们,但是整个混乱会更容易阅读和使用。
-
更一般地说,当你遇到类型错误并且不知道问题出在哪里时,添加一些类型签名通常会帮助你获得更多有用的错误消息。在某些情况下,
ScopedTypeVariables扩展对于为本地绑定提供类型签名是必要的。我不认为这里是这种情况,但我怀疑你的功能无论如何都会更好地分解。 -
我在编写 Haskell 时学到的一件事(这反映了上面的评论):如有疑问,请添加签名!准确地告诉它你想限制它的猜测。大多数时候它可能是很好的猜测类型,但给予它帮助总是会让你受益!
-
顺便说一句,
foldl (++) ""是一种非常低效的连接字符串的方法。快速的方法是foldr (++) "",或者,推广到列表,foldr (++) [],更好地称为concat。