考虑read 函数,它具有(ad-hoc)多态返回值:
read :: (Read a) => String -> a
实施并不那么重要。唯一重要的部分是实现依赖于在编译时选择的Read 的实例,并且推断可能会导致为对read 的相同调用选择不同的类型。
addFive :: Int -> Int
addFive x = x + 5
main :: IO ()
main = do
print (addFive (read "11"))
putStrLn (read "11")
使用相同的参数两次调用read。 Haskell 需要引用透明性,所以它必须两次产生相同的结果,对吧?嗯,不完全是。推断的返回类型很重要。在print 行中,推断的返回类型是Int。在putStrLn 行中,推断的返回类型是String。而且因为它是 ad-hoc 多态的,所以语义会随着类型变量的变化而变化。
print 行将打印出 16。putStrLn 行将崩溃,因为"11" 不是read 将成功解码为String 的输入。
因为类型变量只出现在返回类型中,所以在调用函数时没有该类型的值。没有办法在运行时调度值的类型来确定要使用的 Read 的哪个实例。弄清楚它的唯一方法是在编译时知道类型。所以 Typed Clojure 不能这样做——这意味着语义依赖于编译时类型。
编辑地址评论
我不知道它是否应该给你留下深刻印象。但是由于您的陈述(2)在所有可能的方面都是错误的,这表明即使理解这个例子也明显缺乏基础。我想我必须一直回到 Haskell 中类型变量 的含义 来解释这一点。
Haskell 中的类型变量表示由调用者选择的未知但具体的类型。 Read a => String -> a 类型并不意味着函数根据其输入为其返回值选择一个类型。这意味着该函数根据它输出的类型选择它的工作方式。
也许read 是一个不好的例子,因为它的不同行为只有在由于输入错误而引发异常时才会显得特别不同。对于没有使用 Haskell 类型系统经验的人来说,很容易将其与运行时强制转换异常之类的东西混为一谈,即使它完全不同。
你的说法(2)是完全错误的。该程序不会崩溃,因为read 返回一个Int,其中代码预期为String,并且发生了类似ClassCastException 的情况。程序崩溃是因为read 在编译时根据其返回类型选择了一个解析器来解析String 文字,但给出的输入不是有效的String 文字。 (相比之下,"\"11\"" 是有效的 String 文字,因为它被引用了。)
粗体部分是重要部分。 read 函数根据返回类型选择在编译时要使用的解析器。这既是一种非常强大的技术,也是 Typed Clojure 无法做到的。