Question

See my earlier question about composing opencv operators for an explanation of what is going on.

I thought up a new interface that allows to compose destructive binary operations in a kind of composable way:

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 

With this I can easily express the 'subtract the gaussian operator':

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

To me this seems like a quite safe alternative, though it is not optimal in the sense, that it probably could re-use also the cloned image if some operation after subtract would require this. But it still seems like an acceptable alternative to the completely pure and unoptimized version:

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

Questions

  1. Is this a reasonable structure in the first place?
  2. Is there a reason to prefer the structure in the previous question?
  3. How would you extend this to implement an operation 'find the minimum value in the image, subtract it from the image and then multiply the image with its range (i.e max-min).'
  4. Should I split these into multiple questions instead?
Was it helpful?

Solution

Continuing from hammar's comment, you can just use kleisli composition to begin with, avoiding the IOP altogether. I've kept ImageOp as a type synonym for clarity's sake. Also, I specialized it to always return unit, and changed some other type signatures accordingly so that we have a typed difference between mutating functions (returning unit) and cloining functions (returning a new value), and a function apOp that applies a mutating function and returns the mutated value so that we can chain mutations easily.

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) 

Edit If you feel like it, you can write &#& neatly as follows:

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

Which, I think, is a good argument for this style being pretty expressive.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top