Аппликация без функтора

У меня есть тип 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 этого документа кратко описываются изображения.