我选择使用Haskell的原因是因为rich type system。这在编译时为我提供了有关我的程序的更多信息,帮助我确信它是合理的。
此外,看起来Haskell是一种最佳语言,可以接近expression problem,Haskell typeclasses can dispatch on return type。 (与Clojure协议相反 - 它只能在第一个参数上调度)。
当我探索像read
这样的Haskell多态返回值函数时:
read :: (Read a) => String -> a
使用以下程序:
addFive :: Int -> Int
addFive x = x + 5
main :: IO ()
main = do
print (addFive (read "11"))
putStrLn (read "11")
Runtime error
...
prog: Prelude.read: no parse
所以我似乎在使用优秀类型系统的语言中获得运行时错误。
将此与Clojure中的等效代码进行对比:
(defn add-five [x] (+ 5 x))
(println (add-five (read-string "11")))
(println (read-string "11"))
16
11
我的问题是为什么返回类型多态中的Haskell推断类型导致运行时错误?它不应该在编译时获取它们吗?
答案 0 :(得分:5)
该运行时错误与多态无关,而且与"11"
函数无法将字符串read
解析为字符列表这一事实有关。
以下是有效的方法。请注意,"11"
在运行时可以解析为Int
,"\"Some More String\""
可以在运行时解析为字符串。
print $ 5 + read "11"
print $ "Some string" ++ read "\"Some More String\""
以下是一些不起作用的事情。它们不起作用,因为"Not an integer"
无法解析为Int
,"11"
无法解析为字符串。
print $ 5 + read "Not an integer"
print $ "Some string" ++ read "11"
正如answer to your previous question中指出的那样,类型信息已在编译时推断出来。已选择read
个功能。想象一下,如果我们分别为readInt :: String -> Int
和readString :: String -> String
read
实例的Read
函数提供了两个函数Int
和String
。编译器在编译时已将read
的出现替换为原始的各自函数:
print $ 5 + readInt "Not an integer"
print $ "Some string" ++ readString "11"
这一定必须在编译时发生,因为在编译时消除了类型信息,正如您在上一个问题的答案中所解释的那样。
答案 1 :(得分:4)
这里的问题的一部分是在Haskell中可以定义部分函数,即可能在某些输入上失败的函数。示例包括read
,head
,tail
。非穷举模式匹配是这种偏袒的常见原因,其他包括error
,undefined
和无限递归(即使在这种情况下,您显然没有得到运行时错误)。
特别是,read
有点讨厌,因为它要求您确保可以解析字符串。例如,这通常比确保列表非空是更难的。应该使用更安全的变体,例如
readMaybe :: Read a => String -> Maybe a
main = do
print $ readMaybe "11" :: Maybe Int -- prints Just 11
print $ readMaybe "11" :: Maybe String -- prints Nothing
问题的另一部分是多态值(例如read "11"
)实际上是伪装的函数,因为它们取决于它们被评估的类型,如上例所示。单态限制试图使它们更像非函数:它强制编译器为多态值的所有使用找到单一类型。如果可以,则仅在该类型下评估多态值,并且可以在所有用途中共享结果。否则,即使代码在没有限制的情况下可以输入,也会出现类型错误。
例如,以下代码
main = do
let x = readMaybe "11"
print $ x :: Maybe Int
print $ x :: Maybe Int
如果单态性限制打开则解析11
一次,如果关闭则解析两次(除非编译器足够聪明以进行某些优化)。相比之下,
main = do
let x = readMaybe "11"
print $ x :: Maybe Int
print $ x :: Maybe String
如果启用了单态限制,会引发编译时类型错误,如果关闭则编译并运行正常(打印“Just 11”和“Nothing”)。
因此,在启用和禁用限制之间没有明显的赢家。
答案 2 :(得分:2)
read
的类型是
(Read a) => String -> a
暗示它(实际上是编译器或解释器)将根据上下文的要求选择其返回类型。
因此,在addFive (read "11")
中,由于addFive
需要Int
,因此编译器选择的read
类型将为String -> Int
;在putStrLn (read "11")
中,它会String->String
,因为putStrLn
需要String
。
这个选择发生在编译时,这意味着在编译之后,你的程序等于
main = do
print (addFive (readInt "11"))
putStrLn (readString "11")
但是这个readString
无法将其参数"11"
解析为字符串,因此它在运行时崩溃。
解决这个问题很简单:
main = do
print (addFive (read "11"))
putStrLn (read "\"11\"")