Лучший интерфейс для составления деструктивных операторов, часть II - PullRequest
1 голос
/ 20 мая 2011

См. Мой ранее вопрос о составлении операторов opencv для объяснения того, что происходит.

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

newtype IOP a b = IOP (a -> IO b)

instance Category IOP where
    id = IOP return
    (IOP f) . (IOP g)  = IOP $ g >=> f

(&#&) :: IOP (Image c d) e -> IOP (Image c d) f 
             -> IOP (Image c d) (Image c d,Image c d)
(IOP f) &#& (IOP g) = IOP $ op
    where 
        op i = withClone i $ \cl -> (f i >> g cl >> return (i,cl))

runIOP (IOP f) img = withClone img f 

С этим я могу легко выразить «вычесть гауссовский оператор»:

subtract  :: IOP (Image c d, Image c1 d1) (Image c d)
mulScalar :: d -> IOP (Image c d) (Image c d)
subtractScalar :: d -> IOP (Image c d) (Image c d)
gaussian  :: (Int, Int) -> IOP (Image GrayScale D32) (Image GrayScale D32)

(gaussian (11,11) &#& id) >>> subtract >>> mulScalar 5

Мне кажется, что это вполне безопасная альтернатива, хотя она не является оптимальной в том смысле, что она, вероятно, могла бы повторно использовать и клонированное изображение, если это потребует какая-то операция после вычитания. Но это все еще кажется приемлемой альтернативой полностью чистой и неоптимизированной версии:

mulScalar 5 $ gaussian (11,11) img `subtract` img
-- Or with nicer names for the operators
5 * gaussian (11,11) img - img

Вопросы

  1. Это разумная структура в первую очередь?
  2. Есть ли причина предпочитать структуру в предыдущем вопросе ?
  3. Как бы вы расширили это, чтобы реализовать операцию «найдите минимальное значение в изображении, вычтите его из изображения и затем умножьте изображение на его диапазон (т. Е. Max-min)».
  4. Должен ли я вместо этого разделить их на несколько вопросов?

1 Ответ

2 голосов
/ 20 мая 2011

Продолжая комментарий Хаммара, вы можете просто использовать композицию kleisli для начала, полностью избегая IOP.Ради ясности я сохранил ImageOp как синоним типа.Кроме того, я специализировал его, чтобы он всегда возвращал единицу, и соответственно изменял некоторые другие сигнатуры типов, чтобы у нас была типизированная разница между мутирующими функциями (возвращая единицу) и закрывающими функциями (возвращая новое значение), и функцией apOp, которая применяетФункция мутирования и возвращает мутировавшее значение, чтобы мы могли легко связать мутации.

type ImageOp c d -> Image c d -> IO ()

(&#&) :: ImageOp c d -> ImageOp c d -> (Image c d) -> IO (Image c d, Image c d)
f &#& g = \i -> withClone i $ \cl -> f i >> g cl >> return (i,cl)

apOp :: ImageOp c d -> Image c d -> IO (Image c d)
apOp iop x = fmap x (iop x)

subtract  ::  Image c d ->  ImageOp c1 d1
mulScalar :: d -> ImageOp (Image c d)
subtractScalar :: d -> ImageOp (Image c d)
gaussian  :: (Int, Int) -> ImageOp GrayScale D32

myFun = (gaussian (11,11) &#& id) >=> (\(x,y) -> apOp (subtract x) y) >=> apOp (mulScalar 5) 

--pointfree
myFun = (gaussian (11,11) &#& id) >=> uncurry (apOp . subtract) >=> apOp (mulScalar 5) 

Редактировать Если вам так хочется, вы можете написать &#& аккуратно следующим образом:

f &#& g = uncurry (liftM2 (,)) . (apOp f &&& withClone (apOp g))

Что, я думаю, является хорошим аргументом для того, чтобы этот стиль был довольно выразительным.

...