Proč nemám rád Go
Důvod je velice jednoduchý: Protože v něm nemůžu přímo vyjádřit to, co chci vyjádřit. Vždy musím skákat skrz obruče a dělat to způsobem, který nikdy nevede nejjasnější cestou k cíli.
Hlavní příčinou je chybějící parametrický polymorfismus, v méně civilizovaných
kruzích známý jako generika. Bez něho není snadné vytvářet mocné, ale přesto
bezpečné abstrakce. Nemám na mysli divoké veletoče type level programování a monadické transformátory, ale něco mnohem jednoduššího jako jsou metody map
a filter
— udělej stejnou věc s objekty různých typů.
K téhle litanii mě rozhoupal článek o tom, jak je C++ hrozně nepřehledné a Go je mnohem lepší. Demonstroval to na programu v C++, který pak přepsal do Go. C++ je hrozný zmatek, to ano, Go je o něco málo lepší, ale ne o moc. Stále je v něm příšerně moc šumu a nepotřebných odboček. Připomíná to přirozený jazyk, jehož mluvčí se nikdy nedostane za úroveň pátého ročníku a chybí mu tak slovník pro efektivní pojmenovávání komplexních myšlenek a musí je tak donekonečna opisovat.
Přepsal jsem ukázku do Scaly a až na pár drobností je koncentrace kroků vedoucích za cílem k počtu nutných konstrukcí skoro perfektní. Jako v dobrém scénáři, kde každé slovo posouvá děj k rozuzlení, i tady každý token směřuje jasně k cíli, popisuje, co chci udělat a ne nutné repetitivní detaily, aby byl bůh kompilátorů spokojený. To je ve staticky typovaném jazyce možné právě díky parametrickému polymorfismu, kvůli němuž do sebe můžeme různé komponenty bezpečně zapojit.
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, je tu pár podivností jako .toScala(Iterator)
pro překlenutí propasti mezi
Java a Scala API nebo => ~v
pro sestupné řazení podle intu. Ty ale
nejsou nutné, jde jen o nejpřímější způsoby, jak udělat danou věc. Pokud bych
chtěl něco očividnějšího pro netrénované pozorovatele, mám tu možnost, můžu to
zapsat ve formě smyček a ifů. Na druhou stranu v jazycích bez parametrického
polymorfismu, nemám možnost vytvořit a používat tyto abstrakce a primitivní
pinožení je to nejlepší, v co můžeme doufat.
Napsal jsem to ještě v jazyce D, jehož raison d'être je být příčetné C++ a zase: kód je krátký, přímý, čitelný a k věci.
Téměř každá funkce je ve skutečnosti template, což není úplně to samé co
parametrický polymorfismus, ale poskytuje stejně efektivní a flexibilní API,
které dělá to, co chcete nehledě na přesný typ argumentu. Když zavolám array
,
vytvoří to pole z argumentu nehledě co je zač a jakého typu jsou elementy. Když
má metody pro procházení, výsledkem bude pole.
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); } }
To je všechno, co jsem chtěl říct. Parametrický polymorfismus je klíč k expresivitě, stručnosti a eleganci.