Hacker News new | ask | show | jobs
by agentgt 3681 days ago
I find there are roughly two categories of languages that are worthwhile (based on my opinion of course).

1. Easy to write/produce: Languages/Framework that are easy to write because they are highly expressive (Haskell/Scala) or are very opinionated (Rails).

2. Easy to read/maintain: Verbose languages with excellent tools... cough... Java/C#.

As for reading code I don't know what it is about crappy verbose languages but I have yet to see Java/C# code that I couldn't figure out what was going on. Sure I have more experience with these languages and the tools (particularly code browsing with an IDE) make it so much easier... but so do most people.

The reality is language dilettantes think writing code is painful (as mentioned in the first paragraph by the author) but the real bitch is maintaining.

I feel like there must be some diminishing returns on making a language too expressive, implicit, and/or convenient but I don't have any real evidence to prove such.

4 comments

I would add a third category, which is not exclusive with the other two: languages that provide good tools for abstraction.

The pitfall with #1 is leaky and obscure abstractions. It's easy to write code that has performance problems or requires a lot of understanding of moving parts not actually related to the problem at hand. Where's the code responsible for putting the current state on a web page? All I see is a bunch of monad transformations and I don't know what they're for! Sure, I can figure out what's going on eventually, but I'll have to read a lot of CS papers first.

The pitfall with #2 is lack of ability to write a suitable abstraction for the problem. Instead, the problem has to be fit to the language. You end up with either something relatively simple, but inflexible or a large amount of incidental complexity. Why do I need to implement AbstractThingPutterOnPageGenerator and generate a ThingPutterOnPage before I can put a thing on the page? Couldn't this just be called putThingOnPage() and use some optional args when the default behavior doesn't cut it? Sure, I can figure out what's going on eventually, but I'll have to read a lot of code first.

I think Lisp has always been strong in the third category, and that Clojure is a Lisp especially suited to real-world use right now. The heavy emphasis on defining code in terms of generic operations on generic data structures is a particular strength. For something more mainstream, Python does pretty well here. That's largely cultural though; Python has a very comparable feature set to Ruby, but Ruby's community doesn't have "explicit is better than implicit", the lack of which can lead to code which is impenetrable rather than merely dense.

The problem with Lisps is that generally speaking, they're all interpeted, which means type errors are discovered at runtime. Which sucks for maintenance.
Most implementations of Common Lisp have ahead of time compilation, at least as an option, but also have the compiler, or sometimes a different compiler or an interpreter available at runtime. Clojure is also typically AOT-compiled to JVM bytecode.

Did you mean that Lisps are dynamically-typed? That's true, and whether it's mostly good or mostly bad is a religious topic that almost certainly lacks one true answer. My own take on it is that I program very interactively and static typing feels like an impediment to that most of the time. Furthermore, type errors are usually a small subset of the possible errors and many static type systems allow any type to be null anyway, drastically reducing the benefit.

Common Lisp is compiled and has a type system.

Several type systems.

> I feel like there must be some diminishing returns on making a language too expressive, implicit, and/or convenient but I don't have any real evidence to prove such.

I think it is understood that the more expressive your language is, the more difficult it is to make tools for the language. For example, Common Lisp style (non-hygienic) macros are hard to support in a debugger (by which I mean, hard to allow the developer to step through their code as they wrote it, rather than stepping through the final expanded form). Dynamic dispatch makes it difficult for tools to provide who calls and which function does this call invoke (not impossible, with some forms of static typing, but more difficult in general).

I think I agree with that idea. However I have also wondered if it is because more explicit/verbose languages take longer to physically write and thus the verbose pattern is sort of repeated through out. For example in Java it is typical to have ridiculously long spelled out variable/function names. While this is annoying to write it often makes maintenance slightly easier for a variety of hopefully obvious reasons.

It seems with really expressive languages you get programmers who will use extremely short variable/function names (Haskell being the extreme). Of course this could be just cultural (e.g. Haskell academia). That is it seems when the language gets easy people get lazy :) (this is probably a false assumption).

I'm not sure if its analogous but an extreme opposite of expressive language would be punch cards. My grandmother used to work on ancient computers and you would have to really think ahead what you wanted to do. Consequently lots and lots of documentation would be done.

I've programmed in a couple of languages that one might consider verbose: Java and Common Lisp. The normal tools for both of those provide some form of name-completion. In fact, in Emacs+SLIME, I can do something like this: "(ge-in-ru", hit tab, and have it completed to "(get-internal-run-time". In any case, I consider the typing-out of stuff like that to be a minor part of programming anyway.

Edit: fixed stupid grammatical mistake

I don't think symbol name length is usually what people are complaining about when they call a language verbose. It's usually closer to the number of symbols required to write a program, and CL usually does pretty well on that metric.
I think the real issue is total cognitive load.

There's the cognitive load of the language itself. There's the cognitive load of the libraries. There's the cognitive load of the algorithm. And there's the cognitive load of the actual code (number of lines times how hard each line is to read - smart coding conventions help quite a bit here).

But it's not that simple, because people are different. Different people have different cognitive load when presented with the same language. I think this is one of the reasons Haskell is so polarizing - it either has a low cognitive load for you, or a very high one. And if it has a very high one, you're not likely to spend the time and effort to get to the point where it has a low cognitive load for you.

> I feel like there must be some diminishing returns on making a language too expressive, implicit, and/or convenient but I don't have any real evidence to prove such.

I think that going to far in any of those directions probably increases total cognitive load, by making some other component worse.

I think it's incorrect to point at just the languages though, I feel many methodologies or standards help/hinder this too. And tools. And even, to some extent, communication and/or culture (culture of communication).
I admittedly don't have to read Java or C# code often; a few times I had to though, it had been a fair bit of pain -- so, I would much rather have to figure out someone's Perl code rather than deal with either one of these.

The problem was not with the languages themselves, they are just fine, and I actually quite like C# -- but it seems that a lot of third-party library authors for these languages really go all out on various design patterns, abstracting everything, etc., in the process making simplest things quite impenetrable.

Could have been just my luck though.

It's two extremes of the same problem. Perl code generally doesn't abstract enough, while Java code often abstracts too much. I recently was tasked with porting a legacy perl system to Java. The main pl file of the legacy code was only about ~6k lines, but it also only contained 6 subroutines in the entire thing. The final Java product probably had more actual code glueing everything together and abstracting it, but the meaty parts were much cleaner, simpler, and easier to understand. I was able to show newcomers the new system and within a few minutes they could figure out at least what the major moving parts do, while they would immediately run away form the perl code due to the sheer scariness of it.