在oop中,例如java,当类型实际上是子类时,我们只能将 super 类转发为 subclass .
但是在haskell中,我们可以简单地将'downcast'类型类放入该类型类的任何实例中 . 如 fromInteger
,返回 Num
. 从我的角度来看,它实际上是一个 Int 所以它不能'downcasted'到 Float 但它可以 .
Prelude System.Random> :t fromInteger a
fromInteger a :: Num a => a
Prelude System.Random> fromInteger 12 :: Int
12
Prelude System.Random> fromInteger 12 :: Float
12.0
另一个例子是将 Random
更改为Int,Float甚至Bool
Prelude System.Random> let (a, g) = random (mkStdGen 12) :: (Int, StdGen)
Prelude System.Random> let (a, g) = random (mkStdGen 12) :: (Double, StdGen)
Prelude System.Random> let (a, g) = random (mkStdGen 12) :: (Bool, StdGen)
我们不知道Random实际上是什么,但我们可以将它“转发”为实例的类型,并且它始终100%工作 . 我不明白为什么会这样 .
3 回答
这是你错了 .
fromInteger
有两种不同的实现:Integer -> Int
和Integer -> Float
.fromInteger 12 :: Float
中绝对没有涉及Int
.我认为你错误地将类型类视为OO类并将类型继承与它们相关联而感到困惑 . 类型组是非常不同的,Haskell中没有类型继承,顺便说一句,它根本不是一个弱点 . 您的示例实际上展示了很多Haskell的强大功能 .
让我们分析一下random的定义:
它有一个签名
g -> (a, g)
,它表示它需要一些值g
并返回一些值a
和一些与输入g
相同类型的值,没有像此签名中指定的Int
或Char
这样的特定类型,a
和g
是polymorphic ,意味着它们绝对可以是任何类型 . 接下来是约束部分RandomGen g =>
,它表示实际上g
只能是一个具有类型类RandomGen实例的类型,在类I的接口下面've linked to you' ll找到模块中定义的实例列表,它将仅包含RandomGen StdGen
,所以基本上我们可以将g
视为StdGen
. 然后再看一下random
函数,找出它实际上被定义为类型类Random的接口的一部分,它由类型变量a
参数化,我们已经在函数random
的签名中遇到了,所以这个意味着对函数random
的定义有一个Random a
约束 . 另请参见其实例列表中的此类包含Random Int
,Random Double
,Random Bool
.现在让我们回到你的例子 . 通过指定类型
random (mkStdGen 12) :: (Bool, StdGen)
,您告诉编译器将random
视为random :: StdGen -> (Bool, StdGen)
,从中简单地推断出RandomGen
和Random
要使用的实例 . 这些实例实际上定义了函数的特定于类型的行为,这反过来又保证了任何可编译的代码都有意义 .如你所见,这一切都与铸造完全无关 .
正如您从@MikeHartl的注释中看到的那样,Haskell类型类不是OOP意义上的类 . 它们也不是接口 . 在您的情况下,将它们视为C模板类并将所有方法设置为静态可能很有用:
“铸造”根本不是“铸造”,而是模板参数的明确限定:
同样适用于
Num
: