4 ms·
I hear this perspective a lot on HN. If OO programming is awful, do you have any examples of what you consider to be good code?
by debaserab2 13y ago
I hear this perspective a lot on HN. If OO programming is awful, do you have any examples of what you consider to be good code?
- SwellJoe 13y agoThere's cargo culting on both sides of the question. Because someone heard pg say he's not overly fond of overly OO code (which he has said in the arc design document), or some other well-known thought leader on programming (like Yegge), they then form a particular dislike of all OO code and even the idea of OO, itself. It's not rational or very fully formed for most of the folks who hate OO. Likewise, there are folks coming from (maybe?) a Java or C++ background, who feel that strict OO is the only sane way to approach designing a program. I'm not going to try to give examples of good or bad code in either category...but, will point out that Haskell is not an OO language (in the Grady Booch sense of OO, though you can encapsulate methods in data because it has first class functions), but there's an awful lot of beautiful and correct Haskell code out there. Likewise many other functional, Lisp, and ML-derived languages. OO design can obscure meaning, but there's no reason to get reactionary about it. It's one useful tool; it certainly works great for UIs and some types of API.
- deleted 13y ago[deleted]
- fatjohn 13y agoYou're right, except that Java and C++ don't have much to do with strict OO.
- tel 13y agoI'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 stepTwiceThenShowEm :: Counter -> Int stepTwicethenShowEm = view . step . step and we can easily create alternative implementations intCounter, integerCounter, listCounter, unCounter :: Counter intCounter = Counter succ id (0 :: Int) integerCounter = Counter succ id (0 :: Integer) listCounter = Counter (():) length [] unCounter = Counter id (const 4) () 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. [1] http://hackage.haskell.org/package/folds http://hackage.haskell.org/package/folds
- tel 13y agoBy 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.
- selmnoo 13y agoSince you're specifically asking for examples on non-OO code, and another commenter made mention of Haskell code: try xmonad. Here's a link to get started on studying xmonad's code: http://www.haskell.org/haskellwiki/Xmonad/Guided_tour_of_the_xmonad_source http://www.haskell.org/haskellwiki/Xmonad/Guided_tour_of_the... Incidentally, it's like only 1000 lines of code, so it's super efficient and super optimized.
- rayiner 13y agoI think LLVM is very readable considering the complexity of what it actually does. It uses C++, but for the most part it uses classes to encapsulate names or as dumb data structures. E.g. an analysis might be implemented as a class, but it'll just be one or two classes with a bunch of non-virtual methods operating on dumb data. The dumb data is encapsulated in objects, but the objects don't really have methods that "know" how to do anything other than manipulate the encapsulated data. All that intelligence is in functions that operate on the data. It's very different than typical Java code, where an object will "know" how to do non trivial things to itself, and the pieces of an algorithm end up spread over a dozen classes in a dozen different files. Such code will often use elaborate class hierarchies breaking down pieces of an algorithm into objects that interact with virtual method calls, all because of a crippling fear of switch statements. This cuts off the nose to spite the face, rendering the code incomprehensible as a result.