Most of the Haskell code I see make use of direct structures such as lists and trees. For example, it is common for a Haskeller to write:
fillRect :: Color → Bounds → Image → Image
That pattern has a problem: if later on the programmer decides to modify the definition of "Image", or use another data-structure, then he will have to refactor every single piece of code using it. In OCaml, you can simply use a module specifying an interface to an Image, and then decide upon specific implementations later on.
What is the Haskell alternative to OCaml's modules?
There are several alternatives; none exactly matches ML modules, but each some aspect.
Parametric polymorphism. Rather than parameterising your module and the fillRect
function therein on Image
, you parameterise the abstract Image
type-constructor on something that specifies the particular "kind" of image. So that would be a signature like
fillRect_ppm :: ImageImplemetation i => Colour -> Bounds -> Image i -> Image i
where ImageImplemetation
is some type class that specifies something like conversion and/or backend functions.
With such a solution, you can't really replace the Image
type completely, but you can make the type itself arbitrarily flexible. Likely, all concrete types you'll be using for Image
will actually share some fields, so it's better not to make it more flexible than necessary: different instances of ImageImplemetation
just need to implement what's different between them. If the implementations are very similar, perhaps you want to parameterise on just some detail like the colour space.
Of course you need to plan ahead quite a bit with this solution, but just for a common interface. You can still put in any type as the argument later, provided you define the necessary instances.
Similarly (and possible to mix), you could just have a type class for types that can hold image data.
fillRect_tcl :: Image img => Colour -> Bounds -> img -> img
The Image
class would directly define some primitives like how to draw lines, and fillRect_tcl
would use these in its implementation. Good because fillRect_tcl
works right away with any such type. What's a bit bad is that the Image
class will need to be rather awkwardly big (it's preferred to use type classes for "mathematical" stuff with simple and very clear laws).
Perhaps the class could even have rectangles as a method:
class Image img where
...
fillRect_tcm :: Colour -> Bounds -> img -> img
...
not so nice is, you'll need to completely re-define that method for any img
instance.
Haskell's module system can't do a lot, but still it's sometimes enough to save you from refactoring everything. For instance, if Image
came from a module Data.Image
, you could have done
import qualified Data.Image as IM
fillRect_qfi :: Colour -> Bounds -> IM.Image -> IM.Image
and use all functions defined in that module, also by qualifier. If at some point you decide to switch implementation, you can just change the import.
Of course this isn't much good for switching between types on a regular basis, but quite ok for one-time changes.
Quite fitting to your example: diagrams uses both the first points; e.g. rect uses a TrailLike
class to implement its primitives, and this class includes as "final" diagrams type QDiagram
, which is parameterised on the backend you'll use for rendering.
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