使不需要的转换成为无操作

时间:2014-02-13 07:16:24

标签: image haskell optimization ghc

我正在编写一个程序来读取和写入支持多种像素类型的图像(即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 CUChargetPixel myImage (10, 23) :: RGB CFloat,具体取决于您想要的像素格式。问题是我不知道如何以有效的方式实现fromComponent。从本质上讲,我希望不必要的转换(例如fromComponent (1 :: CUChar) :: CUCharfromComponent (0.5 :: CFloat) :: CFloat)成为无操作。我猜我在任何情况下都必须依赖优化。

注意:这可能不是一个好的设计,所以如果有人有更好的建议我会对此持开放态度。我仍然想知道如何使这个解决方案有效。

2 个答案:

答案 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)