I’m not picking on any specific language throughout this post, I’m mostly just ordering my thoughts about these things. I should probably just use generic names like Brand X, or Language Y, but I’m not going to do that because this will probably be more flow of consciousness that well constructed writing. Keep in mind that I do very little embedded or systems programming where I currently am. I recognize that not everything is web development, however that’s the world I live in currently, and so much of this will probably sound like I’m ignoring anything beyond web (because I am for the time being).
What makes a langauge the best choice for a specific project? Is there any one language that is the best choice for all projects? Does it make sense to try using a single language for all components of your stack? Does it make sense to choose a build tool because it’s written in the language that the rest of the project is written in?
I’ve been thinking about these questions a bit. I’ve read/heard a number of different responses to them. Some people say that scala is the langauge of the gods, and therefore is the best choice for any problem you want to solve. Others love Go, and ask why you’re still working in a JVM language, when Go is the language of the future. Still others ask why don’t you just use NodeJS for everything.
There are definitely a lot of languages which work for a lot of problems - if you really wanted to, you could write a web service using C. However that could (and probably would) lead to a lot of bugs. So you should choose a language which makes it easier to write bug free code. IMHO, (in general) the less code that you write, the better. Writing less code reduces the chance of introducing a bug, and also reduces the code that you have to maintain.
Quick side note - while fewer LOC is generally a good thing, it shouldn’t be the main goal. For example, the following two samples (one Java, one Scalaish) do the same thing. I personally like the functional approach (you can do similar things in Java 8). This example is extremely trivial - all I want to do is filter out some elements from a list, modify the remaining elements, and return the result.
public List doStuff(List list) {
List<?> results = new ArrayList<>();
for(Object item : list) {
if(test(item)) {
results.add(update(item));
}
}
return results;
}
def doStuff(list) = list.filter(test)
.map(update)
In this case, you get the point across more clearly and with less code using the functional approach. However, there are some other examples (stealing from the databricks style guide here), where the shorter version is not easier to understand:
class Person(val data: Map[String, String])
val database = Map[String, Person]
// Sometimes the client can store "null" value in the store "address"
// A monadic chaining approach
def getAddress(name: String): Option[String] = {
database.get(name).flatMap { elem =>
elem.data.get("address")
.flatMap(Option.apply) // handle null value
}
}
// A for-comprehension approach
def getAddress(name: String) =
for {
person <- database.get(name);
address <- person.data.get("address")
} yield address
// A more readable approach, despite much longer
def getAddress(name: String): Option[String] = {
if (!database.contains(name)) {
return None
}
database(name).data.get("address") match {
case Some(null) => None // handle null value
case Some(addr) => Option(addr)
case None => None
}
}
What happens when a person isn’t in the database? What are the possible values of address, and what happens in each case? Those two questions should be immediately obvious from looking at the source code.
The first approach is downright ugly - if you can’t understand what three lines of code are doing nearly instantly, then that code is horribly written. I don’t think scala aficionados would propose approach 1 though. My guess is that they’d say #2 is the way to go.
The issue with #2 is that for comprehensions are not obvious. You could argue that I’m not familiar/comfortable enough with scala to understand them well enough. But if I have to be an expert in the language to understand idiomatic scala code, then scala isn’t the language to use for every day projects. Looking at #2 it’s not immediately obvious what happens when the database does not contain a person, nor is it immediately obvious why (or even that it does) return an Optional[String]. Looking at that, I would expect it to return a String, then have to remind myself that the way this works, it would return an Optional.
Finally #3 - the longest implementation. There are still some issues with this, but of the three I think it’s the clearest. The answers to those two questions are immediately obvious to anyone coming into the source code for the first time.
You know this quickly diverged from what I was originally talking about. I guess my thoughts are that you should choose a language not based on code golf, but based on what make it easier to write clean, maintainable code. That usually involves reducing boilerplate, but also not introducing magic that is incomprehensible.