What Haskell lacks is hierarchies of subtyping. It certainly can be used for OOP. I've written object-oriented code in Haskell. It's not common, but it's not hard. And on rare occasions it's the best solution to a problem.
Haskell does not have OOP facilities built into the language. But then, Haskell arguably doesn't have sequencing of IO built into the language. Standard libraries get the latter done, in many ways better than it's done in other languages. The former lacks a canonical representation provided by any standard (or nearly standard) library I'm aware of, but it's pretty easy to implement the pieces you need yourself once you're aware of the patterns.
Perhaps we have different definitions of OOP. To me, it's not OOP without subclasses and private mutable state. What does OOP mean to you? I'm guessing you're thinking of Haskell's typeclasses as an analogue to Java's interfaces. Any other features?
You can implement private mutable state (and dynamic polymorphism behind an interface) with records of closures. Depending on what you mean by "subclasses" here, they may follow trivially. Haskell typeclasses are a poor match to OOP, as you say.
Some examples. The first is a discussion on it. The second and third have examples in scheme. I'd type more but I'm on my iPad. If I get time later I'll steal time on someone's computer to type up more.
Jtsummers gave some links, but in the interest of having something here:
data Surface = Surface -- presumably provided by drawing backend
data Shape = Shape
{ shapeGetCenter :: (Int, Int)
, shapeSetCenter :: Int -> Int -> Shape
, shapeDraw :: Surface -> IO ()
, shapeScale :: Double -> Shape
, shapeGetAABB :: (Int, Int, Int, Int)
, shapeShow :: String
}
instance Show Shape where
show = shapeShow
circle r x y = Shape
{ shapeGetCenter = (x, y)
, shapeSetCenter = circle r
, shapeDraw = undefined -- draw shape to surface
, shapeScale = \ f -> circle (f * r) x y
, shapeGetAABB =
( floor $ fromIntegral x - r/2
, floor $ fromIntegral y - r/2
, ceiling $ fromIntegral x + r/2
, ceiling $ fromIntegral y + r/2
)
, shapeShow = unwords [ "circle", show r, show x, show y ]
}
box w h theta x y = Shape
{ shapeGetCenter = (x, y)
, shapeSetCenter = box w h theta
, shapeDraw = undefined -- draw shape to surface
, shapeScale = \ f -> box (w * f) (h * f) theta x y
, shapeGetAABB =
let w' = h * abs (sin theta)
+ w * abs (cos theta)
h' = w * abs (sin theta)
+ h * abs (cos theta)
in ( floor $ fromIntegral x - w'/2
, floor $ fromIntegral y - h'/2
, ceiling $ fromIntegral x + w'/2
, ceiling $ fromIntegral y + h'/2
)
, shapeShow = unwords [ "box", show w, show theta, show x, show y ]
}
What's that old chestnut about design patterns just being workarounds for missing language features?
Multi-paradigm languages: I'll admit they're usually woefully lacking in sex appeal. When it comes to getting @$#@% done and moving on with your life, though, they be awesome.
There is a difference between "this isn't provided by the language, but I can easily implement it" and "this is impossible (or absurdly messy) in the language, but these work-arounds can make me care less".