最后,给定足够的宏魔法可以做到这一点......但现在可能比在Clojure上实现Haskell风格的类型系统更省力。类型化的Clojure可能是一个很好的模型,除非它已经明确设计,以便Clojure的语义不受推断类型的影响。这正是返回类型多态中发生的事情,因此在Typed Clojure中显然是不可能的。
我的问题是 - (Haskell的)语义受推断类型(返回类型多态)的影响是什么意思?
答案 0 :(得分:22)
考虑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
行将打印出来。putStrLn
行会崩溃,因为"11"
不是read
成功解码为String
的输入}。
因为类型变量只出现在返回类型中,所以在调用函数时没有该类型的值。在运行时无法分配值的类型以确定要使用的Read
的实例。解决问题的唯一方法是在编译时知道类型。所以Typed Clojure不能这样做 - 它意味着语义依赖于编译时类型。
我不知道它是否应该给你留下深刻印象。但由于你的陈述(2)在各方面都是错误的,它表明即使理解这个例子也显然缺乏基础。我想我必须一直回到Haskell中的类型变量意味着来解释这一点。
Haskell中的一个类型变量代表一个未知但具体的类型,由调用者选择。类型Read a => String -> a
并不意味着函数根据其输入为其返回值选择类型。这意味着该函数根据输出的类型选择它的工作方式。
也许read
是一个不好的例子,因为它的不同行为在由于输入错误而抛出异常时才会显得特别不同。对于没有使用Haskell类型系统经验的人来说,很容易将其与运行时转换异常等内容混淆,即使它完全不同。
你的陈述(2)完全错了。该程序不会崩溃,因为read
会返回Int
,其中代码期望为String
,而ClassCastException
之类的内容会发生。程序崩溃,因为read
选择了一个解析器,用于根据编译时的返回类型解析String
文字,但输入的输入不是有效的{{1}文字。 (相比之下,String
是有效的"\"11\""
字面值,因为它是引用的。)
粗体部分是重要的部分。 String
函数根据返回类型选择在编译时使用的解析器。这是一种非常强大的技术,也是使用Typed Clojure无法做到的。
答案 1 :(得分:4)
看待这种区别的一种方法是检查系统F.除了使用"类型lambdas"明确地引入所有多态之外,它与Haskell非常相似。典型的表示法是类型lambdas在类型声明中出现为" forall"量化(我会写\/
)和值为#34;大lambdas" (我会写/\
)。
因此,例如,id
变为
id :: \/ a . a -> a
id = /\ type -> \x -> x
因此我们必须显式传递实例化变量a
的类型才能使用id
。您可能会将此视为
> id Int 3
3 :: Int
那么,这与返回类型多态有什么关系?好吧,Haskell的类型推理器(Hindley-Milner)系统可以被认为基本上位于系统F的顶部,自动围绕类型进行管道传输。要做到这一点,它限制了System F的很多灵活性,但我们暂时忽略了这一点。
所有您必须记住的是,类型推断器在运行时之前计算出应该将类型变量实例化为什么。或者,更清楚的是,在运行时所有类型的lambda都被删除。这是允许编译时/运行时阶段区分的原因,它允许类型擦除。
Haskell扩展了Hindley-Milner,允许一种有界的多态性。类型
\/ a . C a => a
表示lambda类型只能由C
限定的类型来实现。然后,Haskell解决关于这些边界的方程,以确定 right 类型,以便在任何地方插入。
这是我们获得返回类型多态的地方。当我们引用必须传递给特定类型lambda的类型时,我们使用有关函数输入和输出的信息
f :: a -> b
e :: a
f e :: b
如果函数的返回类型可以约束类型变量,那么它将会。这将使推理器选择正确的 System F 变体。然后,在运行时,所有类型的lambda都消失了,只有与所需返回类型匹配的完全无类型代码仍然存在。