Аппликация без функтора
У меня есть тип Image
, который в основном представляет собой c-массив поплавков. Легко создавать функции
таких как map :: (Float -> Float) -> Image -> Image
или zipWith :: (Float -> Float -> Float) -> Image -> Image -> Image
.
Тем не менее, я чувствую, что также возможно обеспечить что-то, что выглядит как аппликативный экземпляр поверх этих функций, что позволяет использовать более гибкие манипуляции на уровне пикселей, такие как ((+) <$> image1 <*> image2)
или ((\x y z -> (x+y)/z) <$> i1 <*> i2 <*> i3)
. Однако наивный подход терпит неудачу, поскольку тип изображения не может содержать вещи, отличные от плавающих, что делает невозможным реализовать fmap
как таковой.
Как это можно реализовать?
Ответы
Ответ 1
Читая комментарии, я немного волнуюсь, что размер под ковром здесь. Есть ли разумное поведение при несоответствии размеров?
Между тем, может быть что-то, что вы можете разумно сделать по следующим строкам. Даже если ваши массивы нелегко сделать полиморфными, вы можете сделать такой экземпляр Applicative
.
data ArrayLike x = MkAL {sizeOf :: Int, eltOf :: Int -> x}
instance Applicative ArrayLike where
pure x = MkAL maxBound (pure x)
MkAL i f <*> MkAL j g = MkAL (min i j) (f <*> g)
(Энтузиасты заметят, что я взял произведение аппликатора (Int ->)
с тем, что вызвано моноидом (maxBound
, min
)).
Не могли бы вы сделать чистое соответствие
imAL :: Image -> ArrayLike Float
alIm :: ArrayLike Float -> Image
проекцией и таблицей? Если это так, вы можете написать такой код.
alIm $ (f <$> imAL a1 <*> ... <*> imAL an)
Кроме того, если вы хотите обернуть этот шаблон как перегруженный оператор,
imapp :: (Float -> ... -> Float) -> (Image -> ... -> Image)
это стандартное упражнение в программировании стилей! (Спросите, нужно ли вам больше намека.)
Важнейшим моментом является то, что стратегия упаковки означает, что вам не нужно обезьяна с вашими структурами массива, чтобы добавить функциональную надстройку сверху.
Ответ 2
Как вы ожидаете выполнения операций с пикселями в изображении? То есть, для ((+) <$> image1 <*> image2)
, вы хотите выполнить все операции в Haskell и построить новый результирующий образ, или вам нужно будет вызвать функции C для выполнения всей обработки?
Если это первый, ответчик-свинец - это подход, который я бы взял.
Если вместо этого требуется, чтобы все манипуляции с изображениями обрабатывались через C, как насчет создания небольшого DSL для представления операций?
Ответ 3
Вы получите гораздо более композитный тип Image
, если вы обобщите тип "пиксель" из Float
и перейдете от конечного и дискретного домена (массивов) к бесконечной и непрерывной области.
В качестве демонстрации этих обобщений см. Статью Функциональные изображения и соответствующую галерею (конечные выборки) примеры изображений.
В результате вы получаете экземпляры Monoid
, Functor
, Applicative
, Monad
и Comonad
.
Более того, значения этих экземпляров полностью определяются соответствующими экземплярами функций, удовлетворяющими принципу морфизмов класса семантического типа, как описано в статье Denotational design with type класс морфизмов.
В разделе 13.2 этого документа кратко описываются изображения.