Haskell, проблемы с сопоставлением типов между экземпляром и классом типов

#haskell #image-processing #types #monads

#haskell #обработка изображений #типы #монады

Вопрос:

Я пытался выполнить некоторую обработку изображений, преобразовав список целых чисел в вектор пикселей (который имеет те же базовые значения, но разных типов) на основе существующей библиотеки JuicyPixels.

Но когда я попытался сделать свой pixel s изображением, произошла ошибка: пиксель имеет тип Pixel8 , который является экземпляром класса type Pixel px , но ghci сказал мне, что он не может соответствовать этим типам:

     ? Couldn't match type ‘px’ with ‘Pixel8
      ‘px’ is a rigid type variable bound by
        the type signature for:
          makeListImg :: forall px.
                         Pixel px =>
                         Int -> Int -> [Int] -> Image px
        at srcImageHandling.hs:63:1-69
      Expected type: T.MutableImage s Pixel8 -> ST s (Image px)
        Actual type: T.MutableImage (PrimState (ST s)) px
                     -> ST s (Image px)
    ? In the second argument of ‘(>>=)’, namely ‘T.unsafeFreezeImage’
      In the expression:
        makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
      In an equation for ‘img’:
          img
            = makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
    ? Relevant bindings include
        img :: ST s (Image px) (bound at srcImageHandling.hs:66:11)
        makeListImg :: Int -> Int -> [Int] -> Image px
          (bound at srcImageHandling.hs:64:1)
   |
66 |           img = makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage
   |                                                             ^^^^^^^^^^^^^^^^^^^
 

Мои коды (начиная со строки 63):

 makeListImg :: forall px. Pixel px => Int -> Int -> [Int] -> Image px
makeListImg w h lst = runST img
    where img :: ST s (Image px)
          img = makeListMutableImg (w, h) (lst2px8 lst) >>= T.unsafeFreezeImage

lst2px8 :: [Int] -> [Pixel8]
lst2px8 = map toPixel8

makeListMutableImg :: forall m px. (Pixel px, PrimMonad m) => (Int, Int) -> [px] -> m (T.MutableImage (PrimState m) px)
makeListMutableImg (w, h) lst = T.MutableImage w h `liftM` vec
    where elemSize = T.componentCount (undefined :: px)
          vec = do
            arr <- M.new (w * h * elemSize)
            let drawLineFromList :: Int -> Int -> [px] -> m ()
                drawLineFromList _ y _ | y >= h = return ()
                drawLineFromList idx y list = column idx 0 list
                    where column :: Int -> Int -> [px] -> m ()
                          column i x l | x >= w = drawLineFromList i (y 1) l
                          column i x l@(head:tail) = do
                              T.unsafeWritePixel arr i head
                              column (i elemSize) (x 1) tail
            drawLineFromList 0 0 lst
            return arr
 

Это много кодов, и я действительно сожалею об этом, но у меня нет четкого представления о том, что я делаю; Я просто следую идее в библиотеке.

В функции так много монад и абстрактных слоев, поэтому я действительно не знаю, на чем мне следует сосредоточиться. Я хотел скопировать список в изменяемый вектор, MutableImage а затем использовать unsafeFreezeImage и runST преобразовать его в an Image px , но типы остановили меня. Что мне теперь делать и как я могу решить эту проблему?

Комментарии:

1. forall px означает, что ваш код должен работать с любым px , который может выбрать вызывающий вашу функцию, что может быть чем-то отличным от Pixel8 . Если вы охватываете только случай px=Pixel8 , не обещайте «all px es» в вашем типе, используйте Pixel8 вместо этого.

Ответ №1:

Я думаю, что лучшим подходом здесь является Image прямое построение. Это просто обычный тип данных:

 data Image a = Image { imageWidth :: !Int
                     , imageHeight :: !Int
                     , imageData :: Vector (PixelBaseComponent a) }
 

откуда Vector Data.Vector.Storable .

Для an ImageRGB8 , PixelBaseComponent ImageRGB8 is Word8 и Vector Word8 is расположены в порядке следования строк с последовательными компонентами для каждого пикселя. Судя по вашему коду, это то, как вы [Int] уже устроены, поэтому ваша makeListImg функция должна быть такой же простой, как:

 import Codec.Picture
import qualified Data.Vector.Storable as VS

makeListImg :: Int -> Int -> [Int] -> Image PixelRGB8
makeListImg w h = Image w h . VS.fromList . map fromIntegral
 

Я не могу представить приложение, в котором makeListImg была бы полезна полиморфная версия, но можно определить что-то вроде:

 unsafeMakeListImg :: (Integral (PixelBaseComponent p), VS.Storable (PixelBaseComponent p))
  => Int -> Int -> [Int] -> Image p
unsafeMakeListImg w h = Image w h . VS.fromList . map fromIntegral
 

это будет достаточно общим для всех форматов изображений со встроенными пиксельными компонентами. Проблема с попыткой его использования (и причина, по которой я назвал его «небезопасным») заключается в том, что предполагаемый тип для p будет определять достоверность структуры предоставленного списка [Int] . Оригинал makeListImg тоже небезопасен, но, по крайней мере, он небезопасен предсказуемым образом — вы знаете, что обязательным [Int] аргументом всегда являются данные для Image PixelRGB8 .

Отдельный пример:

 import Codec.Picture
import qualified Data.Vector.Storable as VS

makeListImg :: Int -> Int -> [Int] -> Image PixelRGB8
makeListImg w h = Image w h . VS.fromList . map fromIntegral

main = do
  -- make a left-to-right red gradient
  let foo = makeListImg 100 100 $ concat [[156 x,0,0] | x <- [0..99], y <- [0..99]]
  saveJpgImage 75 "foo.jpg" $ ImageRGB8 foo
 

Комментарии:

1. Спасибо за ответ! Я бы попытался переопределить свою функцию по-вашему