我刚从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,而不是Ints
。
Pixel8
只是Word8
的一个类型别名。拔了头发之后,我发现将Int
转换为Word8
的方法是使用fromIntegral
。
fromIntegral
的类型签名是(Integral a, Num b) => a -> b
。在我看来,这个函数实际上并不知道要将它转换为什么,所以它将转换为非常通用的Num
类。所以从理论上讲,它的输出是符合类型类Num
的任何类型的变量(如果我在这里弄错了--据我理解,类有点像“接口”,其中类型更像OOP中的类/原语)。如果我给变量赋值
let n = fromIntegral 5
:t n -- n :: Num b => b
所以我想知道..。什么是“b”?我可以将这个变量作为任何变量使用,并且它将隐式转换为任何数字类型,就像它看起来的那样。它不仅将隐式转换为Word8
,还将隐式转换为Pixel8
,这意味着根据上下文,fromPixel
有效地从(据我理解) (Integral a, Num b) => a -> b
转换为(Integral a) => a -> Pixel8
。
有人能确切地说明一下这里发生了什么吗?为什么我可以使用通用Num
作为符合Num
的任何类型,无论是机械的还是“道德的”?我不明白隐式转换是如何实现的(如果要创建自己的类,我觉得需要添加显式转换函数)。我也不知道为什么会这样;在这里,我可以使用非常不安全的类型,并将其隐式地转换为任何其他类型。(例如,如果我将fromIntegral 50000
隐式转换为Word8
,它将被转换为Word8
)
发布于 2015-10-04 18:15:24
类型类(如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
”。
像您的n :: Num b => b
这样的值没有什么不同。它们被编译成如下所示
n :: (Integer -> b) -> b
n dictFromInteger = dictFromInteger 5 -- roughly
正如您所看到的,这会将看似无辜的整数文本转换为函数,这会(而且确实)影响性能。但是,在许多情况下,编译器可以意识到实际上不需要完整的多态版本,并删除所有字典。
例如,如果编写f 3
但f
需要Int
,则可以在编译时转换“多态”3
。因此,类型推断可以帮助优化阶段(用户编写的类型注释在这里可以提供很大的帮助)。此外,还可以手动触发其他一些优化,例如使用GHC SPECIALIZE
实用程序。最后,可怕的单态限制在翻译后很难强迫非函数保持非函数,代价是多态性的损失。然而,MR现在被认为是有害的,因为它会在某些情况下造成令人费解的类型错误。
https://stackoverflow.com/questions/32936592
复制相似问题