10

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?

aleator
  • 4,436
  • 20
  • 31
  • 3
    Why wouldn't you allow an Image to contain thing other than floats? Sure, your `display :: Image Float -> IO ()` could only take images with floats, but for other functions like `map` it wouldn't matter. – Tom Lokhorst Aug 11 '11 at 11:20
  • 1
    The image type is an c array that needs to be passed to c functions, without spending too much time in doing conversions. Also, I'd assume, that if it could hold things like functions, having few million partially evaluated functions after call to `pure` would be rather ugly performance-wise. (Implementing `pure` by having an Image of functions is also problematic since it cannot know the size of the image.) – aleator Aug 11 '11 at 12:04
  • can you define your type like `type Image = CArray Float` and make a Functor instance for CArray with fmap being your map function, and you make sure you can't make a CArray of anything but CArray Float (don't export the constructor, for example) – David V. Aug 11 '11 at 12:30
  • 3
    Possibly you would want to design from an abstract `Image` type rather than work from its concrete memory representation. For instance, in Conal Elliott's Pan and Chalkboard from U.Kansas - masks are represented as `Image Bool` a function that determines whether a pixel is within the mask. `Image RGB` is an image of RGB values that can be rendered as a bitmap. If you need these different types for Image you will need a more general representation. – stephen tetley Aug 11 '11 at 15:02

3 Answers3

15

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.

pigworker
  • 43,025
  • 18
  • 121
  • 214
  • Great! Pretty much exactly what I was looking for. Can't figure out why I didn't see it myself. Did you mean making N instances for N different arity functions, or something more clever by the typeclass comment? – aleator Aug 12 '11 at 09:49
  • 2
    You should just need two instances: a base case for arity 0 (or 1 if you need one array input to bound the size of the output), and a step case. Hang on (sorry code is grotty in comments)... `class NAry ff fi | ff -> fi, fi -> ff where { naryHelp :: (ArrayLike (Float -> ff)) -> (Image -> fi); nary :: (Float -> ff) -> (Image -> fi); nary = naryHelp . pure} ; instance NAry Float Image where { naryHelp f i = alIm $ f <*> imAL i } ; instance NAry ff fi => NAry (Float -> ff) (Image -> fi) where { naryHelp f i = naryHelp (f <*> imAL i) }` – pigworker Aug 12 '11 at 10:49
  • 1
    Just an additional thought: `ArrayLike` is basically quite like the repa delayed arrays, right? Atleast it is plain to see how I could easily insert parallel computations in `alIm` and define `force` as `imAl . alIm`. – aleator Aug 12 '11 at 20:06
  • 1
    I don't know the repa details, but it sounds a lot like we're coming from the same place (APL). I'm sure they've thought about it a lot more than I have. But yes, the implementation of `alIm` is your chance to distribute labour, and `imAL . alIm` replaces any old array-like computation with projection from an actual array. – pigworker Aug 12 '11 at 20:17
6

How would you expect to perform operations on pixels in an image? That is, for ((+) <$> image1 <*> image2), would you want to perform all the operations in Haskell and construct a new resulting image, or would you have to call C functions to do all the processing?

If it's the former, pigworker's answer is the approach I would take.

If instead it's required that all image manipulations be handled via C, how about creating a small DSL to represent the operations?

John L
  • 27,937
  • 4
  • 73
  • 88
  • The image would be manipulated mostly by C. What I'm after is a way to make simple pixel level operations in haskell - it is tiresome to drop all the way to c for adding a simple function like atan2. – aleator Aug 12 '11 at 04:57
6

You'll get a much more compositional Image type if you generalize the "pixel" type from Float and extend from finite & discrete domain (arrays) to infinite & continuous domain. As a demonstration of these generalizations, see the paper Functional Images and a corresponding gallery of (finite samplings of) example images. As a result, you get instances of Monoid, Functor, Applicative, Monad, and Comonad. Moreover, the meanings of these instances are entirely determined by the corresponding instances for functions, satisfying the principle of semantic type class morphisms, as described in the paper Denotational design with type class morphisms. Section 13.2 of that paper briefly describes imagery.

Conal
  • 18,517
  • 2
  • 37
  • 40