this post was submitted on 07 May 2025
152 points (94.2% liked)

Programmer Humor

35561 readers
220 users here now

Post funny things about programming here! (Or just rant about your favourite programming language.)

Rules:

founded 5 years ago
MODERATORS
 
you are viewing a single comment's thread
view the rest of the comments
[–] [email protected] 16 points 2 days ago (1 children)

For sure, it's a lot easier to do a lot of stuff today than before, but the way we build software has become incredibly wasteful as well. Also worth noting that some of the workflows that were available in languages like CL or Smalltalk back in the 80s are superior to what most languages offer today. It hasn't been strictly progress in every regard.

I'd say the issue isn't that programmers are worse today, but that the trends in the industry select for things that work just well enough, and that's how we end up with stuff like Electron.

[–] [email protected] 4 points 2 days ago (1 children)

Also worth noting that some of the workflows that were available in languages like CL or Smalltalk back in the 80s are superior to what most languages offer today.

In what ways? I don't have any experience with those so I'm curious.

[–] [email protected] 10 points 2 days ago (5 children)

Common Lisp and Smalltalk provided live development environment where you could run any code as you write it in the context of your application. Even the whole Lisp OS was modifiable at runtime, you could just open code for any running application or even the OS itself, make changes on the fly, and see them reflected. A fun run through Symbolics Lisp Machine here https://www.youtube.com/watch?v=o4-YnLpLgtk

Here are some highlights.

The system was fully introspective and self-documenting. The entire OS and development environment was written in Lisp, allowing deep runtime inspection and modification. Every function, variable, or object could be inspected, traced, or redefined at runtime without restarting. Modern IDEs provide some introspection (e.g., via debuggers or REPLs), but not at the same pervasive level.

You had dynamic code editing & debugging. Functions could be redefined while running, even in the middle of execution (e.g., fixing a bug in a running server). You had the ability to attach "before," "after," or "around" hooks to any function dynamically.

The condition system in CL provided advanced error handling with restarts allowed interactive recovery from errors (far beyond modern exception handling).

Dynamic Window System UI elements were live Lisp objects that could be inspected and modified interactively. Objects could be inspected and edited in structured ways (e.g., modifying a list or hash table directly in the inspector). Modern IDEs lack this level of direct interactivity with live objects.

You had persistent image-based development where the entire system state (including running programs, open files, and debug sessions) could be saved to an image and resumed later. This is similar to Smalltalk images, but unlike modern IDEs where state is usually lost on restart.

You had knowledge-level documentation with Document Examiner (DOCX) which was hypertext-like documentation system where every function, variable, or concept was richly cross-linked. The system could also generate documentation from source code and comments dynamically. Modern tools such as Doxygen are less integrated and interactive.

CL had ephemeral GC that provided real-time garbage collection with minimal pauses. Weak references and finalizers are more sophisticated than most modern GC implementations. Modern languages (e.g., Java, Go, C#) have good GC but lack the fine-grained control of Lisp Machines.

Transparent Remote Procedure Calls (RPC) allowed Objects to seamlessly interact across machines as if they were local. Meanwhile NFS-like but Lisp-native file system allowed files to be accessed and edited remotely with versioning.

Finally, compilers like Zeta-C) could compile Lisp to efficient machine code with deep optimizations.

[–] [email protected] 1 points 2 days ago (1 children)

Unfortunately, the lisp machine didn’t gain traction because the start-up times were so long and I believe this is due to it doing lots of internal checks which was awesome but unfortunately things like the Sun SPARCstation won because it was crazy fast although buggy

[–] [email protected] 2 points 2 days ago (1 children)

It was basically way ahead of its time.

[–] [email protected] 2 points 1 day ago

It really was. I forgot to mention in my comment that the sun machines were also really cheap so, you know, capitalism.

[–] [email protected] 3 points 2 days ago (1 children)

I had access to a Symbolics machine back in the day, but I was too young & dumb to understand or appreciate what I had my hands on. Wasted opportunity 😔

[–] [email protected] 2 points 2 days ago

It's like an artifact from an ancient and more advanced civilization. :)

[–] [email protected] 2 points 2 days ago* (last edited 2 days ago) (1 children)

I love Lisp, that's why I like doing industry work in JS, because it's very lisp like.

However if you gave an average industry programmer Lisp today they'd fuck up so much worse than the garbage enterprise grade language code that exists today. I switch jobs probably every 4 years and on average I teach 3 people a year what a closure is.

Lisp has a lot of great solutions for a lot of real problems, but these people quite literally fix one bug and create 3. I had a 10+ YOE Tech Lead tell me the other day that they kinda just ignore the error output of the TS compiler and it made me want to tear my eyes out.

[–] [email protected] 1 points 2 days ago (1 children)

I guess I've been lucky, I've been working with Clojure professionally for over a decade now, and every team I've worked on was very competent. Could be that there's a selection bias at play with teams that use a language like Clojure though since it tends to appeal to experienced developers.

[–] [email protected] 1 points 1 day ago (1 children)

I've found it hard to find jobs with Clojure/Haskell/Rust. I typically look for interesting projects and industries that don't make me feel icky even though they end up doing so because everything is fucking enterprise sales. My career has kinda been Bar Rescue for idiot companies who have blundered into an extremely hard problem and need someone to actually figure it out before the software stack implodes on itself.

[–] [email protected] 1 points 1 day ago (1 children)

Clojure jobs are definitely around, I got involved in the community early and wrote a few libraries that ended up getting some use. I also joined local Clojure meetup, and ended up making some connections with companies using it. I've also worked in a team lead position in a few places where I got to pick the tech stack and introduced Clojure. I didn't find it was that hard to hire for it at all. While most people didn't know Clojure up front, most people who applied were curious about programming in general and wanted to try new things.

[–] [email protected] 1 points 1 day ago* (last edited 1 day ago) (1 children)

Yeah I just haven't really held out for one. At one point I have this fear that on average regardless of language I'm gonna see the same shit everywhere, so I typically pick by project interest and scale. If I wasn't such a little cockroach about having a stable income I could have had some fun opportunities holding out for some Haskel, Erlang or Clojure jobs, but I didn't.

I was once interviewed by a startup that was a crypto payments processor targeting the central American market and the interviewer let it slip that I shouldn't worry about runway because it comes from a fairly large crypto fund that the founder owns that's payed into by USAID/NED style soft intelligence services.

I immediately got the ick and I was like this is not something I want to involve myself in for stability's sake but god damn I could have had a peek behind the curtain.

[–] [email protected] 1 points 1 day ago (1 children)

I found Clojure jobs were generally pretty interesting. One of my jobs was working at a hospital, and we were building software for patient care. So we got to go to the clinics within the hospital observe the workflow, builds tools for the users, and then see how it improved patient care day to day. It was an incredibly rewarding experience.

For me, the language matters a lot, and Clojure is the only language that I've used for many years that I'm still excited to write code in. Once you've worked with a workflow that's fully interactive, it's really hard to go back. I really enjoy having instant feedback on what the code is doing and being able to interrogate the app any time I'm not sure what's happening. This leads to an iterative development process where you always have confidence that the code is doing exactly what you expect because you're always exercising it, and experimentation become much easier. You can just try something see the result, and then adjust as you go.

[–] [email protected] 1 points 1 day ago (1 children)

I found Clojure jobs were generally pretty interesting. One of my jobs was working at a hospital, and we were building software for patient care. So we got to go to the clinics within the hospital observe the workflow, builds tools for the users, and then see how it improved patient care day to day. It was an incredibly rewarding experience.

Sounds like you got double lucky. Hasn't really been my experience in the medical space. I find larger institutions like that very unreceptive to how software is made and often the environments are constricting and lead to bad outcomes that "nobody can really figure out why". It often starts at timesheets and gets worse from there.

For me, the language matters a lot, and Clojure is the only language that I’ve used for many years that I’m still excited to write code in. Once you’ve worked with a workflow that’s fully interactive, it’s really hard to go back. I really enjoy having instant feedback on what the code is doing and being able to interrogate the app any time I’m not sure what’s happening. This leads to an iterative development process where you always have confidence that the code is doing exactly what you expect because you’re always exercising it, and experimentation become much easier. You can just try something see the result, and then adjust as you go.

Yeah you can definitely have this kind of stuff in other languages. It's gonna be similar workflows that are generally BDD & REPL based but you have to have someone who knows what they're doing do architecture, tooling selection, setting conventions, and helping to put it all together into a maintainable system. Very often that's skipped at most companies, and I've found it to be a lucrative skill in my career.

[–] [email protected] 1 points 1 day ago (1 children)

Yeah you can definitely have this kind of stuff in other languages.

It's not even remotely comparable. Outside Lisps, I have not seen any environment where you can start up your app, connect the editor to it, and then develop new code in the context of a running application. I also find that language design very much impacts conventions and architecture. Clojure's focus on immutability naturally leads to code that's largely referentially transparent and where you can reason about parts of the application in isolation without having to consider side effects and global state. Meanwhile, focus on plain data avoids a lot of the complexity you see in OOP languages. Each object is basically a state machine with an ad hoc API on top of it. You end up having to deal with graph of these opaque stateful entities, which is incredibly difficult to reason about. On the other hand, data is inert and transparent. When you pass data around, you can always simply look at the input/output data and know what the function is doing. Transforming data also becomes trivial since you just use the same functions regardless of what data structure you're operating on, this avoids many patterns like wrappers and adapters that you see in OO style. My experience with Clojure is that its semantics naturally lead to lean systems that are expressed in terms of data transformation pipelines.

Again, this is my personal experience. Obviously, plenty of people are working with mainstream languages and they're fine with that. Personally, I just couldn't go back to that now.

[–] [email protected] 1 points 1 day ago* (last edited 1 day ago) (1 children)

Outside Lisps, I have not seen any environment where you can start up your app, connect the editor to it, and then develop new code in the context of a running application.

This is absolutely true, however I don't particularly value this feature because most engineers typically already cannot separate concerns very well in industry so IMO if I had this I would not want people to use it. Very much a "it works ship it" trap.

. I also find that language design very much impacts conventions and architecture. Clojure’s focus on immutability naturally leads to code that’s largely referentially transparent and where you can reason about parts of the application in isolation without having to consider side effects and global state.

I'm with you here. I basically force almost every code base I end up working on into functional forms as much as possible.

When you pass data around, you can always simply look at the input/output data and know what the function is doing. Transforming data also becomes trivial since you just use the same functions regardless of what data structure you’re operating on, this avoids many patterns like wrappers and adapters that you see in OO style

Yep agreed, almost every role I step into I push the org to enforce this style of work.

Transforming data also becomes trivial since you just use the same functions regardless of what data structure you’re operating on, this avoids many patterns like wrappers and adapters that you see in OO style.

This is where you lose me, you still have wrappers and adapters, they're just not classes. They're functions. I still use those words regardless if I'm in Haskell or Typescript. Semantic meaning shouldn't be lost in functional style because it's part of architecture. Functional programming simply gives your basic building blocks better names and better division of responsibility, e.g. functor, applicative, monad, etc.

[–] [email protected] 1 points 1 day ago (1 children)

This is absolutely true, however I don’t particularly value this feature because most engineers typically already cannot separate concerns very well in industry so IMO if I had this I would not want people to use it. Very much a “it works ship it” trap.

That's been the opposite of my experience using Clojure professionally. You're actually far more likely to refactor and clean things up when you have a fast feedback loop. Once you've figured out a solution, it's very easy to break things up, and refactor, then just run the code again and make sure it still works. The more barriers you have there the more likely you are to just leave the code as is once you get it working.

This is where you lose me, you still have wrappers and adapters, they’re just not classes.

A good explanation of the problem here https://www.youtube.com/watch?v=aSEQfqNYNAc

When you're dealing with types or classes they exist within the context they're defined in. Whenever you go from one context to another, you have to effectively copy the data to a new container to use it. With Clojure, you have a single set of common data structures that are used throughout the language. Any data you get from a library or a component in an application can be used directly without any additional ceremony.

[–] [email protected] 1 points 1 day ago* (last edited 1 day ago) (1 children)

That’s been the opposite of my experience using Clojure professionally. You’re actually far more likely to refactor and clean things up when you have a fast feedback loop. Once you’ve figured out a solution, it’s very easy to break things up, and refactor, then just run the code again and make sure it still works. The more barriers you have there the more likely you are to just leave the code as is once you get it working.

This is more of a how the saussage is made issue in my experience than a tooling selection issue. Clojure may make it easier to do the right thing but the actual forcing function is the company culture. Self-selection of Clojure as the company's tooling may create a correlation.

Most companies have the ability to implement fail fast workflows for their developers they simply choose not to because it's "hard". My preferred one is Behavior Driven Development because it forces you to constrain problems into smaller domains/behaviors.

When you’re dealing with types or classes they exist within the context they’re defined in. Whenever you go from one context to another, you have to effectively copy the data to a new container to use it. With Clojure, you have a single set of common data structures that are used throughout the language. Any data you get from a library or a component in an application can be used directly without any additional ceremony.

An Adapter is typically a piece of code that transforms data between formats at various boundaries. Typeclasses remove the need for Adapters for functionality at library boundaries e.g. most thing have map where in javascript I can't do {}.map with the EMCA standard. However typeclasses do not solve the problem of the literal data format and functionality differences between different implementations.

For example I call some API using a Client and it returns bad gross data based on how that API is written, I would use an Adapter to transform that data into clean organized data my system works with. This is extremely helpful when your system and the external system have references to each other, but your data taxonomy differs.

A real example is that Google Maps used to have a distance matrix API where it would literally calculate matrices for you based on the locations you submit. Circa 2018 Google changed it's billing driving up the prices, which lead a lot of people to use alternative services like Here.com. Here.com does not have a distance matrix API. So in order to build a distance matrix I needed to write an adapter that made N calls instead of Google's 1 call and then stuff the Here.com responses into a matrix response compatible with Google's API which we unfortunately were using directly without an internal representation.

These concepts are still used/useful within functional contexts because they are not technical concepts they are semantic concepts. In functional languages an Adapter may just be a function that your responses are mapped over, in OOP style it might be a class that calls specific network client and mangles the data in some other way. Regardless of the technical code format, it still is the same concept and procedural form, thus it's still a useful convention.

[–] [email protected] 1 points 1 day ago (1 children)

I agree, the language alone isn't a silver bullet. I'm not suggesting that it is. You still have to implement good workflow, do testing, code reviews, architecture design, and so on. All these things are language agnostic. What the language can do is reduce friction in your workflow, and nudge design in the right direction by making it easier to do the right thing. I largely see it as a quality of life improvement.

Also, I'm not saying that patterns like adapters don't have their uses or that you might not use a similar approach in a functional language. My point was that these types of patterns tend to be more pervasive in mainstream languages.

Static typing itself is a trade off as well. It introduces mental overhead because you are restricted to a set of statements that can be expressed using a particular type system, and this can lead to code that's written for the benefit of the type checker rather than a human reading it. Everything is a trade off in practice.

Finally, the choice of language ultimately depends on a particular team. Different people think in different ways, and have different experience. The best language is the one that majority of the team is comfortable using. Hence, I'm speaking here from my personal perspective of the way I enjoy doing development. This will necessarily vary from person to person.

[–] [email protected] 1 points 1 day ago (1 children)

Static typing itself is a trade off as well. It introduces mental overhead because you are restricted to a set of statements that can be expressed using a particular type system, and this can lead to code that’s written for the benefit of the type checker rather than a human reading it. Everything is a trade off in practice.

You mean code that's written to the benefit of a low efficiency enterprise workflow, which is my love hate relationship with Typescript. Best out choice out of a pile of shit.

[–] [email protected] 1 points 1 day ago (2 children)

That's not what I'm saying. I think static typing introduces a certain set of trade offs that some people prefer. You restrict the set of statements that are possible to express to ones that can be checked by the type system, and as a result you get additional compile time guarantees. For example, Lemmy devs prefer this trade off and it has nothing to do with enterprise workflows.

[–] [email protected] 1 points 1 day ago* (last edited 1 day ago) (1 children)

you are restricted to a set of statements that can be expressed using a particular type system

What I'm saying is that most good static typing systems do not practically have such limitations, you'd be very hard pressed to find them and they'd be fairly illogical. Most static typing systems that are used in enterprise do have limitations because they are garbage.

So in such shitty type systems you often have code that’s written for the benefit of the type checker rather than a human reading it. In good type systems any code that's written for the benefit of the type checker is often an antipattern.

For example, Lemmy devs prefer this trade off and it has nothing to do with enterprise workflows.

Rust has HKT support through GATs and typeclass support thru traits. Rust has minimal code you write for the benefit of the type checker.

Typescript technically has HKT support but it's a coincidence and the Typescript team doesn't care about it, since the beginning Typescript was made to be Enterprise Javascript by Microsoft. Though systems like fp-ts exist they're hard to get rolling in enterprise.

Typescript does have problems with code that’s written for the benefit of the type checker rather than a human reading it in a large part due to inefficiencies of the compiler itself. In a small part due to some corner cases that still exist because even though it's type system while more advanced than others in it's enterprise grade class, it's still written in that style for that purpose so the inconsistencies it makes to support the janky workflow (plus some EMCA stuff e.g. Promise is not functionally typeable since the spec breaks set theory for convenience reasons) leads to that problem.

However in Typescript these are avoidable problems and you are able to write code without dealing with the type checker's bullshit a good amount of the time if you follow the correct patterns -- certainly better than any other "enterprise grade" static typing system.

[–] [email protected] 1 points 1 day ago* (last edited 1 day ago)

What I’m saying is that most good static typing systems do not practically have such limitations, you’d be very hard pressed to find them and they’d be fairly illogical. Most static typing systems that are used in enterprise do have limitations because they are garbage.

Of course they do, it's silly to claim otherwise. Some type systems are certainly more flexible than others, but each one necessarily restricts how you can express yourself. Not to mention the fact that advanced type systems introduce mental overhead of their own. The more flexible the type system is the more complex it is as a result. There was even famously a debugger for Scala type system illustrating just how absurd things can get. I've used plenty of typed languages including Haskell, so I understand perfectly well how modern static typing works.

Meanwhile, I'd argue that Typescript provides incredibly weak guarantees in practice, and the impact of transpiling on the workflow is not insignificant.

My experience is that immutability plays a far bigger role than static typing. The best pattern for ensuring correctness and maintainability is to break things up into small components that can be reasoned about independently. Any large project can be broken up into smaller parts, and that's by far the best approach towards ensuring correctness that I've seen.

Again, that's my experience working with many different languages for over two decades now. I'm not suggesting other people can't have their own preferences.

[–] [email protected] 6 points 2 days ago (1 children)

No wonder there are some older developers who defend Lisp so passionately. Sounds like a dream to work with once you got the hang of it.

[–] [email protected] 2 points 2 days ago

It's really impressive to think what was achieved with such limited hardware compared to today's standards. While languages like Clojure are rediscovering these concepts, it feels like we took a significant detour along the way.

I suspect this has historical roots. In the 1980s, Lisp was primarily used in universities and a small number of companies due to the then-high hardware demands for features like garbage collection, which we now consider commonplace. Meanwhile, people who could afford personal computers were constrained by very basic hardware, making languages such as C or Fortran a practical choice. Consequently, the vast majority of developers lacked exposure to alternative paradigms. As these devs entered industry and academia, they naturally taught programming based on their own experiences. Hence why the syntax and semantics of most mainstream languages can be traced back to C.

[–] [email protected] 4 points 2 days ago

Interesting! Thank you!