0xDEADBEEF

RSS odkazy
««« »»»

Why I don't like Go

27. 10. 2020 #en

The reason is very simple: Because I cannot directly express what I want to express. All the time I'm forced to jump through hoops and do everything in a convoluted way which never leads directly to desired target.

The main reason is missing parametric polymorphism, in less civilized lands also known as the generics. Without it it's not possible to create powerful and safe abstractions. Dijkstra said „The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise“ and he was absolutely right. Now don't be confused, I'm not talking about type level programming, monad transformers and other confusing rubbish, my desires are much more pedestrian, I'm talking about methods like map and filter — do the same thing with objects of different types.

This rant was initiated by article talking at length how C++ is terrible and Go is much better alternative. Author demonstrated his point on a sample C++ program, which he rewrote to Go. Even modern C++ is a horrible mess, no doubt about it, but Go is not much better. There's still too much noise and useless diversions. It feels like a natural language spoken by somebody who never progressed past 5th grade level. As such he's missing vocabulary needed to effectively communicate complex thoughts and he's doomed to describe them over and over again.

I rewrote the sample to Scala and basically everything in the program leads directly towards desired target. It's like a good screenplay where every word moves a plot towards a conclusion. There too almost every token is dealing with a problem at hands and i don't have to handle unnecessary repetitive details to make the god of compilers happy. In a statically typed language this is possible precisely because of the parametric polymorphism.

import scala.collection.mutable
import scala.jdk.StreamConverters._
import java.nio.file._

val wordRegex  = "(?i)([a-z]{2,})".r
val wordCounts = mutable.Map[String, Int]().withDefaultValue(0)

for {
  f <- Files.walk(Paths.get(".")).toScala(Iterator)
  if Files.isRegularFile(f) && f.toString.endsWith(".txt")
  str = io.Source.fromFile(f.toFile, "utf-8").mkString
  word <- wordRegex.findAllIn(str.toLowerCase)
} wordCounts(word) += 1

wordCounts.toSeq.sortBy { case (k, v) => ~v }.take(10).foreach(println)

Ok, there's a few strange bits like .toScala(Iterator) to bridge a gap between Scala and Java API, or => ~v to descending sort by integer. Those are not necessary, only the most direct ways how to do those things. If I would like to do it in a way more obvious for untrained observer, I could. I could write it all by loops and ifs. On the other hand in languages without parametric polymorphism, there's no other way, that choice of a better abstraction is not possible and primitive tinkering is the best what we can hope for.

Also I wrote the program in D language whose raison d'être is to be more readable and sane C++ and again: the code is short, direct, readable and to the point. Almost every function is a template, not the same as parametric polymorphism, but still it allows very flexible and concise API.

import std.file;
import std.regex;
import std.string;
import std.array;
import std.algorithm;
import std.stdio;

void main() {
  auto wordRegex = regex("([a-z]{2,})", "i");
  int[string] wordCounts;

  foreach (f; dirEntries(".", "*.txt", SpanMode.depth)) {
    if (!isFile(f)) continue;
    auto str = readText(f).toLower;

    foreach (m; matchAll(str, wordRegex)) {
      wordCounts[m[0]]++;
    }
  }

  auto wordsArray = wordCounts.byKeyValue.array;
  auto top10 = topN!"a.value > b.value"(wordsArray, 10);

  foreach (pair; top10) {
    writeln(pair.key, " ", pair.value);
  }
}

And that's everything I wanted to say. Parametric polymorphism is the key to expressivity, brevity and elegance. But on the other hand what do I, as non-developer, actually know…

píše k47 (@kaja47, k47)