Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Lisp really is an eye-opening experience when learning to structure programs and computations. Stumbling on Lisp in college was a fascinating experience for me in particular because I had been raised around C++ and Java, and my programming life up to that point had been a struggle understanding how to abstract a program into objects and how to build those objects' interfaces.

Once I started down the path of functional programming and Clojure, it became clear in retrospect that "objects" as C++ and Java envisioned them were poor fits for most problem domains, which is why their purpose never clicked in my head. Reducing data down to...well, data, and methods down to functions, and programs down to pyramids of expression calls made everything about program structure click in my head. Functional, expression-based style was FAR more intuitive to my brain than stateful object style. I now try to lean heavily into expressions and referential transparency no matter what language I use, though some languages make it easier than others.

Funnily enough, C program structure came much more naturally to me than either C++ or Java. Maybe because it's easy to decouple data from operations on that data, even if stateful. Hard to say.



A big piece of OO development is state management. If you're modeling a system having several interacting entities whose interactions may vary based off that entity's current state - then OO is most likely the way to go. You can encapsulate that state management in one place rather than having it strewn throughout the entire codebase.

If you're doing information-oriented processing, whether it be a traditional batch process, or a decision-support system reacting in real time to incoming data - then functional is most like the way to go.

If you're doing corporate CRUD development, then just use JavaScript (assuming Web CRUD) and the popular CRUD framework du jour.

This is what it means to use the right tool for the right job. There is no perfect language that excels at everything. It all depends on what you need to do.


You don't need OO for state management. Plain data structures and functions work just fine.

The core aspects of OO like inheritance are not that useful for most problems.


> You don't need OO for state management.

Of course you don't, that's why I said OO is most likely the way to go.

I also spoke of more than simple state management - If you're modeling a system having several interacting entities whose interactions may vary based off that entity's current state - isn't simple state management.

You still don't have to go OO to solve that problem, it's just a very good choice in most circumstances.

Also, inheritance is not a core aspect of OO. It's a common aspect of OO and even a popular aspect, but it's not core. To wit, there are OO systems not having inheritance.


> not a core aspect of OO

One of the core aspects of OO is reuse of functionality, typically by inheritance or delegation. We can find one or both in most OO languages.


I wouldn't say reuse of functionality is a core aspect of OO, but I will say that's a core reason why OO was so heavily adopted in the 90s. It's mind-numbing to recall how much time and money was wasted on making things "reusable" only to never be reused.


> I wouldn't say reuse of functionality is a core aspect of OO

You'll find it in practically every object-oriented language, starting with Simula. Later Smalltalk, Actors, CLOS, C++, Self, Java, Objective-C, Python, JavaScript, ...

> making things "reusable" only to never be reused.

It's similar mind-numbing how much has been reused.


> It's similar mind-numbing how much has been reused.

My experience is the reuse comes from frameworks. I'd love to hear if you've had another experience.


Yes, there are a bunch of frameworks, which make use of inheritance and/or delegation.

Flavors (an early object-oriented system for Lisp, early 80s) provided multiple-inheritance, mixins, method combinations, etc. and a bunch of software made use of its features. Object Lisp was another OO system for Lisp, which one used for example in an interface builder, which made extensive use of delegation. The Common Lisp Object System later was designed improving on Flavors and LOOPS (from Xerox), switching away from message passing, adding multiple dispatch. Thus dispatch based on inheritance works over possibly multiple arguments.

Inheritance and delegation have a long history in Lisp going back to the 70s.


> I wouldn't say reuse of functionality is a core aspect of OO,

That's what was taught to me in computer science at university, along with Java (sadly).


IMO inheritance is more syntactical QoL than core. That’s just code DRY stuff and composition is fine for that too. The architectural elements of OO are state machine modeling (encapsulation) and message passing.


The Common Lisp Object System does not use message passing.


It does have method dispatching, which is the same beast with a different name.


Message passing means that objects communicate only via messages with some payload. There is a sender and a receiver. Possibly there are many receivers in a broadcast message. Messages will run synchronous or asynchronous. The receiver decides what to do with the message and how to interpret it. If it does not understand the message, it can forward the message via inheritance, delegation or similar. The message implementation belongs to the class and/or object. There is a syntax for sending messages. The mental model of a developer is based on communicating objects.

In Common Lisp the object system uses generic functions, which assemble several related methods. Generic Functions are defined outside (!) of classes and objects. Generic Functions are both CLOS objects and functions. Generic functions are called with one or more arguments. The Generic Function decides which methods are run and in what combination. There is no equivalent to a receiver: CLOS generic functions dispatch over its arguments, all primary arguments. There is no equivalent of a privileged receiver object or a self. There is no special syntax for calling a generic function and they are themselves first class objects, which can be stored and passed around. The mental model of a developer is based on function calling of runtime assembled functions.


The classic mistake most programmers made with OO is to translate the business domain objects to classes with behavior. While this allowed for some interesting cases like with Naked Objects, it was usually not the most useful way to analyze a system and encapsulate application functionality.

The better way to employ OOA&D was to divide the system functions up into classes. You have an object that manages connection pools and an object that serializes data, and an object that manages tasks... These were the more useful abstractions, because for business systems we're generally not doing simulation.

The state of the system encapsulated in these objects was the operational state of the stack, not the user state which was just more data to throw around. Abstracted this way, however, you get roughly the same kind of system over and over again. How boring! That way leads to frameworks... Which is also what you saw in the late 90s and early 2000s. It's because for most business applications the abstractions of the system, as opposed to the business domain, are so very close to the same that you can, in fact, codify those into cookie-cutter apps where only the domain data being passed around differs.

Functional programming starts you in this state of abstraction, which is why it tends to be simpler for business systems.


CL can do OO programming too.


Common Lisp is one of the few truly multi-paradigm languages.

It's annoying the standard has not evolved a bit to bring some ideas you see in Clojure, or some computation models featured in CTM & Mozart/Oz that have made it to Racket.

Still, a fantastic language. And the ecosystem is far from stagnant, with things like https://clasp-developers.github.io and https://coalton-lang.github.io coming out lately.


What killed Scala at my last employer was that nobody could agree on what idiomatic code should look like, so there were vast stylistic differences between areas. Despite working in the same language, it ended up being hard to move between codebases, in unpredictable ways.

This strikes me as the same sort of flexibility: you can do just about anything. If you do do just about anything, it's a bad time for all concerned.


Common Lisp has CLOS (Common Lisp Object System), which is quite sophisticated object system with metaobject protocol and multiple dispatch, as a part of its spec. Not that it's smth people need to invent on their own each time


Well yes. The problem isn't people building their own implementation. The problems with Scala aren't with them inventing their own type system, or pattern matching, or whatever. It's the lack of agreement on what is the idiomatic subset of features to use in a given environment.


The Meta-Object Protocol is optional and not a part of the standard.


It would be far more consistent than the Scala in the example, if everyone used SBCL with the same implementation, right?


I would think that Scala is slightly more object-oriented than Common Lisp, given that one can mostly ignore OOP in some Common Lisp applications. Thus that part already (macros !) makes CL very flexible in expressing different ways of programming. CLOS without MOP has a range of possible use. I haven't thought about how consistent typical code actually is, using CLOS without MOP. My intuition would say that there is some common CLOS style, but maybe that's wrong. It would be an interesting question to look at, how CLOS code bases make use of its features and if they share common architecture principles. Actually there is not that many literature on software engineering principles for CLOS. For example see: "CLOS in Context: The Shape of the Design Space" by Bobrow&Gabriel&White.

https://www.dreamsongs.com/Files/clos-book.pdf

The MOP itself was thought to make CLOS programmable and thus to support a wide range of different object-oriented styles in a single language framework. In practice there is use of that, but it's not that common and actually also a bit complicated/tricky/challenging to program the object system in itself for different behavior.


Every modern CL compiler (SBCL, CCL, ECL)... will implement it 'de facto'.


It's just that "it" has no defined standard. See https://github.com/pcostanza/closer-mop/blob/master/features... for the MOP differences on platforms which have support.

Closer to MOP is a library which tries to fix many/most of these differences.

https://github.com/pcostanza/closer-mop


The OO patterns are still quite popular in enterprise software, where people mostly work in large teams. Hiding implementation details is terribly annoying for a single developer, but can be a great way to reduce complexity for other team members.

The same consideration applies to other powerful constructs, such as static typing, garbage collection, operator overloading, and macros.

Whether you work alone or in a team can significantly influence how one appreciates such features.


There’s nothing stopping you from hiding implementation details in a functional paradigm, no need for objects or other OO patterns.


I could certainly be wrong, but that doesn't seem to be the case to me.

When you completely separate routines from the data, you end up coupling the code that calls the routine with the data it’s passing. I don’t see how it would be possible to maintain that level of separation while achieving the same level of isolation that’s possible with objects, where it’s possible to truly know nothing about the data in a given interaction.

I’m referring here to the purest aspect, in terms of pure functions. At some point, the data has to come from an impure source, and honestly, I don’t see why a closure would be better than an object—practically, they’re the same thing.


> Maybe because it's easy to decouple data from operations on that data, even if stateful

You can use objects in C++ and Java without mixing code and data (by having data-only and code-only classes for example).

I haven't fully read it, but there's a book from a clojure developer about about this: https://www.manning.com/books/data-oriented-programming

The book itself uses Java, not Clojure.


I know Java programmers use static-method classes, which are just like modules in newer languages. Which just further illustrates the point. It's a hack on top of a language that requires everything to be defined inside of a class. That layer of abstraction on top of things that could be much simpler kills understanding. It massively complicates answering the question "What even is a class?" for a learner. I also realize C++ has simple structs, and classes can be avoided entirely, but that's definitely not the way the language is taught.

Maybe I needed to "git gud", but learning Lisp enabled a career building valuable products currently in production. Java and C# were a roadblock on the way to my current productivity.


I didn't expect that much negativity regarding my comment, I never implied you need to "git gud".

I'm happy in my Clojure job and I would rather not work with OO languages, and that's definitely not how they're taught, but if/when you're forced to use them you can choose not to follow the traditional way and instead write them in a more functional/data oriented way.


I didn't intend a negative tone and that's hard to convey in text.


>The book itself uses Java, not Clojure.

Almost all of the code examples in the book are in JavaScript (not Java) though a significant feature of Sharvit's approach is that it decouples Data Oriented Programming from any specific language. As a Clojure geek, I highly recommend the book as the way to achieve some of Clojure's core virtues in other languages.


Sorry for going off-topic, but why is this comment being downvoted?

The comment provides an alternative view on things and even links to a nice book. One may not agree with the perspective, but that's no reason to downvote.

Perhaps it's the user interface (a special upvoting wand would be most welcome for those of us whose fingers are too fat). Or have we reached the stage where script kiddies deploy downvoting bots if they didn't like a comment in another thread?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: