I have a type Image
which is basically an c-array of floats. It is easy to create functions
such as map :: (Float -> Float) -> Image -> Image
, or zipWith :: (Float -> Float -> Float) -> Image -> Image -> Image
.
However, I have a feeling that it would also be possible to provide something that looks like an applicative instance on top of these functions, allowing more flexible pixel level manipulations like ((+) <$> image1 <*> image2)
or ((\x y z -> (x+y)/z) <$> i1 <*> i2 <*> i3)
. However, the naive approach fails, since Image type cannot contain things other than floats, making it impossible to implement fmap
as such.
How could this be implemented?
Applicative functors are the programming equivalent of lax monoidal functors with tensorial strength in category theory. Applicative functors were introduced in 2008 by Conor McBride and Ross Paterson in their paper Applicative programming with effects.
Monads are not a replacement for applicative functors Instead, every monad is an applicative functor (as well as a functor).
Functors are very useful in modeling functional effects to apply a function to computations that did not yet finish. Functors form a base for more complex abstractions like Applicative, Monad, Comonad.
The expression fmap (*2) is a function that takes a functor f over numbers and returns a functor over numbers. That functor can be a list, a Maybe , an Either String, whatever. The expression fmap (replicate 3) will take a functor over any type and return a functor over a list of elements of that type.
Reading the comments, I'm a little worried that size is under the carpet here. Is there a sensible behaviour when sizes mismatch?
Meanwhile, there may be something you can sensibly do along the following lines. Even if your arrays aren't easy to make polymorphic, you can make an Applicative
instance like this.
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)
(Enthusiasts will note that I've taken the product of the (Int ->)
applicative with that induced by the (maxBound
, min
) monoid.)
Could you make a clean correspondence
imAL :: Image -> ArrayLike Float
alIm :: ArrayLike Float -> Image
by projection and tabulation? If so, you can write code like this.
alIm $ (f <$> imAL a1 <*> ... <*> imAL an)
Moreover, if you then want to wrap that pattern up as an overloaded operator,
imapp :: (Float -> ... -> Float) -> (Image -> ... -> Image)
it's a standard exercise in typeclass programming! (Ask if you need more of a hint.)
The crucial point, though, is that the wrapping strategy means you don't need to monkey with your array structures in order to put functional superstructure on top.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With