Улучшенный интерфейс для составления деструктивных операторов, часть II

StackOverflow https://stackoverflow.com/questions/6069015

  •  07-09-2020
  •  | 
  •  

Вопрос

Смотрите мои предыдущие вопрос о составлении операторов 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. Должен ли я вместо этого разделить их на несколько вопросов?
Это было полезно?

Решение

Продолжая комментарий Хаммара, вы можете просто использовать композицию Клейсли для начала, полностью избегая ВГД.Для большей ясности я сохранил 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))

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top