任意Num如何包含任何其他数字类型?

时间:2015-10-04 18:00:40

标签: haskell

我刚刚开始使用Haskell,我想我是从制作随机图像生成器开始的。我环顾了一下,发现了JuicyPixels,它提供了一个叫做generateImage的简洁功能。他们给出的例子似乎没有开箱即用。

他们的例子:

imageCreator :: String -> IO ()
imageCreator path = writePng path $ generateImage pixelRenderer 250 300
   where pixelRenderer x y = PixelRGB8 x y 128

当我尝试这个时,我得到generateImage期望Int -> Int -> PixelRGB8pixelRenderer的类型为Pixel8 -> Pixel8 -> PixelRGB8PixelRGB8属于Pixel8 -> Pixel8 -> Pixel8 -> PixelRGB8类型,因此pixelRenderer进行某种类型推断以确定xy类型为Pixel8是有意义的}。如果我定义一个类型签名,声明它们是Int类型(因此该函数被generateImage接受,PixelRGB8抱怨它需要Pixel8 s,而不是{{ 1}}。

Ints只是Pixel8的类型别名。在一些头发拉动后,我发现将Word8转换为Int的方法是使用Word8

fromIntegral的类型签名是fromIntegral。在我看来,该函数实际上并不知道您要将其转换为什么,因此它转换为非常通用的(Integral a, Num b) => a -> b类。所以从理论上讲,这个输出是任何类型的变量,适合类型类Num(如果我错在这里,请纠正我 - 正如我所理解的那样,类是有点像"接口"其中类型更像是OOP中的类/基元。如果我指定一个变量

Num

所以我想知道......什么是' b'?我可以将这个变量用作任何东西,它会隐式地转换为任何数字类型,就像看起来一样。它不仅会隐式转换为let n = fromIntegral 5 :t n -- n :: Num b => b ,而且会隐式转换为Word8,这意味着Pixel8会有效地从(我理解)fromPixel转变为{{} 1}}取决于上下文。

有人可以澄清一下这里发生了什么吗?为什么我可以使用通用(Integral a, Num b) => a -> b作为符合(Integral a) => a -> Pixel8的任何类型,机械和"道德和#34;?我不明白隐式转换是如何实现的(如果我要创建自己的类,我觉得我需要添加显式转换函数)。我也不知道为什么这样有效;在这里我可以使用非常不安全的类型并将其隐式转换为其他任何类型。 (例如,如果我隐式将其转换为Num,则Num会转换为fromIntegral 50000

1 个答案:

答案 0 :(得分:3)

Num等类型类的常见实现是字典传递。粗略地说,当编译器看到像

这样的东西时
f :: Num a => a -> a
f x = x + 2

它将其转换为类似

的内容
f :: (Integer -> a, a -> a -> a) -> a -> a
  -- ^-- the "dictionary"
f (dictFromInteger, dictPlus) x = dictPlus x (dictFromInteger 2)

后者基本上说:"为我的类型Num传递类a的这些方法的实现,我将使用它们生成函数a -> a for你"

n :: Num b => b之类的值也不例外。它们被编译成诸如

之类的东西
n :: (Integer -> b) -> b
n dictFromInteger = dictFromInteger 5   -- roughly

正如您所看到的,这会将无辜的整数文字转换为函数,这些函数可以(并且确实)影响性能。但是,在许多情况下,编译器可以意识到实际上并不需要完整的多态版本,并删除所有字典。

例如,如果你写f 3f期望Int,那么"多态" 3可以在编译时转换。因此,类型推断可以帮助优化阶段(用户编写的类型注释可以在这里有很大帮助)。此外,可以手动触发一些其他优化,例如,使用GHC SPECIALIZE编译指示。最后,可怕的单态性限制试图强迫非函数在翻译后保持非函数,但代价是多态性的某些丢失。然而,MR现在被认为是有害的,因为它可能在某些情况下引起令人费解的类型错误。