我刚刚开始使用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 -> PixelRGB8
而pixelRenderer
的类型为Pixel8 -> Pixel8 -> PixelRGB8
。 PixelRGB8
属于Pixel8 -> Pixel8 -> Pixel8 -> PixelRGB8
类型,因此pixelRenderer
进行某种类型推断以确定x
和y
类型为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
答案 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 3
但f
期望Int
,那么"多态" 3
可以在编译时转换。因此,类型推断可以帮助优化阶段(用户编写的类型注释可以在这里有很大帮助)。此外,可以手动触发一些其他优化,例如,使用GHC SPECIALIZE
编译指示。最后,可怕的单态性限制试图强迫非函数在翻译后保持非函数,但代价是多态性的某些丢失。然而,MR现在被认为是有害的,因为它可能在某些情况下引起令人费解的类型错误。