我正在编写一个程序来读取和写入支持多种像素类型的图像(即RGB,CMYK,灰度等)。这些像素类型可以使用不同类型的组件,如下所示:
class (Storable c) => PixelComponent c where
blackWhite :: c -> (c, c)
toInt :: c -> Int
toRealFrac :: (RealFrac a) => c -> a
fromComponent :: (PixelComponent a) => a -> c
instance PixelComponent CUChar where
blackWhite x = (minBound x, maxBound x)
toInt = id
toRealFrac = fromIntegral
fromComponent x = ???
instance PixelComponent CFloat where
black = 0.0
white = 1.0
toInt = truncate
toReal = id
fromComponent x = ???
class (Storable pix) => Pixel pix where
red :: pix c -> c
green :: pix c -> c
blue :: pix c -> c
alpha :: pix c -> c
luminance :: pix c -> c
fromPixel :: (Pixel a) => a c -> pix c
您的想法是,您应该能够getPixel myImage (10, 23) :: RGB CUChar
或getPixel myImage (10, 23) :: RGB CFloat
,具体取决于您想要的像素格式。问题是我不知道如何以有效的方式实现fromComponent
。从本质上讲,我希望不必要的转换(例如fromComponent (1 :: CUChar) :: CUChar
和fromComponent (0.5 :: CFloat) :: CFloat
)成为无操作。我猜我在任何情况下都必须依赖优化。
注意:这可能不是一个好的设计,所以如果有人有更好的建议我会对此持开放态度。我仍然想知道如何使这个解决方案有效。
答案 0 :(得分:6)
我建议以与GHC处理数字转换(例如fromIntegral
)相同的方式处理此问题,这是通过重写规则进行的。
如果您查看GHC.Real,就会找到
-- | general coercion from integral types
fromIntegral :: (Integral a, Num b) => a -> b
fromIntegral = fromInteger . toInteger
{-# RULES
"fromIntegral/Int->Int" fromIntegral = id :: Int -> Int
#-}
对于整数类型,默认是很多往返,但幸运的是,从未发生过因为所有库提供的整数类型都有RULE。
例如,GHC.Int中指定了更多RULE来处理剩余的转换。您会找到其他类似功能的类似设置(例如realToFrac
)。
现在你的用例存在一个主要问题,那就是RULEs通常很难在类方法上进行匹配。有两种方法可以解决这个问题。第一种是定义一个公共类型(例如GHC代码中的Integer
),并提供转换为该类型的类方法。然后编写一个通用转换函数(例如fromIntegral
),在任何地方使用它,并让你的RULE匹配。
另一种方法是做这样的事情:
instance PixelComponent CUChar where
blackWhite x = (minBound x, maxBound x)
toInt = id
toRealFrac = fromIntegral
{-# INLINE fromComponent #-}
fromComponent = toCUChar
toCUChar :: PixelComponent a => a -> CUChar
toCUChar = ...
{-# RULES "fromComponent/CUChar->CUChar" toCUChar = id :: CUChar -> CUChar #-}
前者是GHC所做的,所以它很可能运作良好。我最近一直在使用后一种方法,但没有任何问题,所以任何一个都应该有效。
答案 1 :(得分:0)
多参数类型类可能是您的朋友,但请记住,您必须在此处输入n^2
次内容。
这是一个简单的例子,假设我们有小写字符串,大写字符串和混合字符串的单独类型。显然,将字符串转换为它自己的类型不应该需要任何工作,也不应该转换为混合字符串。只转换到不是自身的上部或下部字符串应该需要转换。
以下代码(ideone)说明了这种方法:
{-# LANGUAGE MultiParamTypeClasses #-}
import Data.Char (toUpper, toLower)
data Lower = Lower String deriving Show
data Mixed = Mixed String deriving Show
data Upper = Upper String deriving Show
toUpperStr = map toUpper
toLowerStr = map toLower
class Make a where
make :: String -> a
instance Make Lower where
make s = Lower (toLowerStr s)
instance Make Mixed where
make s = Mixed s
instance Make Upper where
make s = Upper (toUpperStr s)
class Convert a b where
convert :: a -> b
instance Convert Lower Lower where
convert = id
instance Convert Lower Mixed where
convert (Lower s) = Mixed s
instance Convert Lower Upper where
convert (Lower s) = Upper (toUpperStr s)
instance Convert Mixed Lower where
convert (Mixed s) = Lower (toLowerStr s)
instance Convert Mixed Mixed where
convert = id
instance Convert Mixed Upper where
convert (Mixed s) = Upper (toUpperStr s)
instance Convert Upper Lower where
convert (Upper s) = Lower (toLowerStr s)
instance Convert Upper Mixed where
convert (Upper s) = Mixed s
instance Convert Upper Upper where
convert = id
main = print (convert ((make "Hello World") :: Lower) :: Upper)