Melhor interface para composição de operadores destrutivos, Parte II
-
07-09-2020 - |
Pergunta
Veja meu anterior pergunta sobre a composição de operadores opencv para uma explicação do que está acontecendo.
Eu pensei em uma nova interface que permite compor operações binárias destrutivas de uma forma combinável:
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
Com isso posso expressar facilmente o 'subtrair o operador gaussiano':
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
Para mim, esta parece ser uma alternativa bastante segura, embora não seja ideal no sentido de que provavelmente poderia reutilizar também a imagem clonada se alguma operação após a subtração exigir isso.Mas ainda parece uma alternativa aceitável à versão completamente pura e não otimizada:
mulScalar 5 $ gaussian (11,11) img `subtract` img
-- Or with nicer names for the operators
5 * gaussian (11,11) img - img
Questões
- Esta é uma estrutura razoável em primeiro lugar?
- Existe uma razão para preferir a estrutura do anterior pergunta?
- Como você estenderia isso para implementar uma operação 'encontre o valor mínimo na imagem, subtraia-o da imagem e depois multiplique a imagem por seu intervalo (ou seja, máximo-mínimo).'
- Devo dividi-las em várias perguntas?
Solução
Continuando com o comentário de hammar, você pode simplesmente usar a composição kleisli para começar, evitando completamente a PIO.Mantive ImageOp como sinônimo de tipo para maior clareza.Além disso, especializei-o para sempre retornar a unidade e alterei algumas outras assinaturas de tipo de acordo, para que tenhamos uma diferença digitada entre funções de mutação (retornando unidade) e funções de clonagem (retornando um novo valor) e uma função apOp
que aplica uma função mutante e retorna o valor mutado para que possamos encadear mutações facilmente.
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)
EditarSe você quiser, você pode escrever &#&
ordenadamente da seguinte forma:
f &#& g = uncurry (liftM2 (,)) . (apOp f &&& withClone (apOp g))
O que, creio eu, é um bom argumento para que esse estilo seja bastante expressivo.