I'd like to try pushing the Haskell-is-an-OO-language bit a little further. I think Haskell98 is lacking, but with existential types you can model process entities in a way that I think is often underappreciated. As an off-the-cuff example, consider a counter. A simple approximation might be to say that (Int, Int -> Int) models a counter because it models the state of one, but it also fixes the implementation and fails to encapsulate.
But we can do better with existential types
data Counter =
forall x . Counter { _step :: x -> x
, _view :: x -> Int
, _state :: x
}
step :: Counter -> Counter
step c@(Counter {..}) = c { state = _step _state }
view :: Counter -> Int
view (Counter {..}) = _view _state
At this point, we're passing around the internal state as "some type x" which can never be inspected, achieving encapsulation. We can create general functions that manipulate Counters
We also now must start thinking of our "objects" as entities because we cannot compare their internal state for equality (unless we give it an Eq constraint). This begins to quickly demand we keep some notion of state as we need to model "pointer equality" pretty quickly.
We can even do HAS A subtyping. Lenses make this very powerful.
data TwoCounters =
forall x . TwoCounters { _c1 :: Lens' x Counter
, _c2 :: Lens' x Counter
, _state :: x
}
There's no doubt that this is an uncommon technique in Haskell, but it's worth stating that there are a LOT of similarities between codata modeling like this and typeclasses.
Finally, I began to follow a lot of this due to a middling popular library `folds` [1] which describes very generic ways of traversing data structures and accumulating some kind of result state. Basically all of the types dealt with in this library (L1, L1', M1, R1, L, L', M, R) are existentially quantified codata "objects" like I described above. This makes for very general code.
By the way, for those who aren't familiar with Lenses, it's not a bad thing to think of them as getter/setter pairs (which should be a little unsurprising at this point). They're first class, more principled, and more generalizable than your typical getter/setter pair... but my use of them here is far from unintentional.
But we can do better with existential types
At this point, we're passing around the internal state as "some type x" which can never be inspected, achieving encapsulation. We can create general functions that manipulate Counters and we can easily create alternative implementations We also now must start thinking of our "objects" as entities because we cannot compare their internal state for equality (unless we give it an Eq constraint). This begins to quickly demand we keep some notion of state as we need to model "pointer equality" pretty quickly.We can even do HAS A subtyping. Lenses make this very powerful.
There's no doubt that this is an uncommon technique in Haskell, but it's worth stating that there are a LOT of similarities between codata modeling like this and typeclasses.Finally, I began to follow a lot of this due to a middling popular library `folds` [1] which describes very generic ways of traversing data structures and accumulating some kind of result state. Basically all of the types dealt with in this library (L1, L1', M1, R1, L, L', M, R) are existentially quantified codata "objects" like I described above. This makes for very general code.
[1] http://hackage.haskell.org/package/folds