2024-10-13

opinions on programming languages

there are many programming languages - thousands, in fact - and the number continues to grow. informally, "programming language" often refers to a combination of syntax, semantics, compiler implementations, available libraries, extensions, and the surrounding community. however, at its core, a programming language is defined by its syntax and semantics. these are the fundamental elements, as they allow a developer to precisely instruct a computer. everything else, such as tools and software ecosystems, emerges from this foundation. for this reason, syntax and semantics are the most critical factors when evaluating a programming language.

learning a language and mastering its intricacies requires significant investment. becoming highly proficient can take years. most people learn the languages that their company requires, those recommended to them, or ones that are popular due to effective marketing. once learned, these languages often become a lifelong preference, with developers rarely considering alternatives. language popularity is often influenced by the success of programs written in that language, but this is not necessarily indicative of the languages intrinsic qualities.

initial language choices are frequently arbitrary, but subsequent choices are often constrained by past experience. for example, after spending two weeks learning language x, a developer might opt to use it for a new project, rather than adopting language y, which would require additional time and effort to learn. over time, this dependency grows. why change tools when the current one, while imperfect, offers a familiar and somewhat predictable workflow?

there is also the intellectual challenge of solving problems in different languages. some tasks, easily solved in one language, may be more laborious in another. at times, developers find themselves grappling with problems that would not exist had a different tool been chosen. however, such issues are rarely obvious upfront, and finding solutions can be an engaging process.

the age of a language matters little. languages evolve, and an older design can endure if it is fundamentally strong. implementation quality is key. in practice, it is often more common to see popular but flawed languages gradually improved over time, rather than developers switching to languages built on superior foundations. for instance, complex and inconsistent syntaxes can persist simply because the language's ecosystem is well-established.

readability

the term "readability" is often equated with ease of thought, but this perception is typically the result of extensive practice. reading code is an exercise in extracting information from visual patterns, which involves several cognitive processes. these include returning to the start of a line, recognizing distinct elements through whitespace, and associating sequences of characters with abstract concepts. unlike machines, humans face cognitive costs, and even small, repetitive mental tasks can accumulate into significant strain, reducing mental bandwidth over time.

notation allows varying degrees of flexibility, as different formatting choices can make code look distinct while preserving the same underlying structure. for example, whitespace is frequently optional, resulting in diverse formatting styles based on individual authors' preferences for whitespace usage.

language ranking

while the following list does not go far beyond opinion, i only rate languages which i have used professionally for multiple days.

tier 1

c

adhering to the "principle of least surprise". offers low-level control with a high degree of freedom, particularly in memory manipulation. most features map closely to their underlying implementation, though undefined and unspecified behavior varies across compilers. static typing, manual memory management, and direct access to memory allow for fine-grained control. highly optimized compilers exist for numerous platforms. however, it lacks modern features such as a module system, proper scope management, memory ownership semantics, and hygienic macros, making it both tedious and risky to use. despite its shortcomings, no superior alternative offers the same level of control and performance. there is also sc, which is c written with s-expressions.

coffeescript

its syntax excels for didactic purposes, merging the principle of least surprise with a syntactic elegance reminiscent of scheme. compiling directly to javascript, it offers a streamlined, decluttered alternative while remaining fully compatible with the javascript ecosystem, including node.js. coffeescript + javascript -> agility. its indentation-based syntax ensures readability and coherence. coffeescripts expressiveness comes from well-recognizable patterns derived from a minimal set of constructs, making it also ideal for configuration files - simpler and more intuitive than yaml, ini, json, or xml. perfect for code examples and algorithm learning, it is expressive and close to pseudocode.

scheme

schemes syntax and semantics prioritize pragmatic and autodidactic efficiency, making it one of the simplest languages to learn, parse, and translate.

  • it supports unobstructed functional abstraction, scaling seamlessly across paradigms. everything, from metaprogramming to custom syntaxes, is achievable through its fundamental building blocks.
  • scheme grants freedoms most languages restrict, like a wide range of characters for custom identifiers. its minimal syntax - just round brackets, no semicolons, braces, or commas - reduces clutter.
  • s-expression notation is generic and consistent, allowing simple structural editing and macro design. homoiconicity enhances flexibility and readability.
  • while the use of round brackets is ubiquitous, their consistency and the absence of other delimiters simplify managing complexity. concise, self-similar notation paired with plain english identifiers fosters readability and reduces cognitive load.
  • schemes formally specified standards (eg, r5rs) contrast with the ad-hoc evolution seen in many languages, promoting clarity and reliability.
  • i find scheme allows for faster, error-free development compared to other languages.

criticisms

  • certain r6rs features are questionable:

    • inconsistent naming schemes (e.g., flonums with "fl" prefix).
    • square bracket alternatives to round brackets add syntactic ambiguity and hinder code editing.
    • module system indentation adds unnecessary verbosity.
  • file extensions (.sls, .ss) reflect file purpose rather than syntax or processor, unlike .scm.
  • i dislike the common nested-expression indentation style; regular indent sizes better convey complexity.
  • the use of ;; and ;;; for comments feels redundant.
  • guile scripts require two lines for shebangs.
  • more imports are often needed compared to languages like shell or ruby.
  • scheme suffers from a lack of unity: multiple implementations, varied module systems, and bracket ambiguities detract from cohesion.
  • naming conventions like "cons" and "cdr" (and their derivatives) feel cumbersome.
  • i struggle with the quote syntax ('`,).

tier 2

javascript

expressive but syntactically noisy (semicolons, though optional, are often used; the mix of square, curly, and round brackets adds to the clutter). it offers many syntactically distinct ways to achieve similar results (e.g., object literals, constructor functions, classes). asynchronous handling is convoluted, with async/await, promises, and continuation-passing styles often coexisting awkwardly. implicit type conversions, truthy/falsy values, and global variables (without var) introduce potential bugs. despite these quirks, javascripts first-class functions evoke similarities to scheme.

  • node.js for system scripting: fast startup, useful libraries (e.g., commander.js), but theres always a risk of bloating simple scripts with package.json and unnecessary asynchronous complexity.

ruby

relatively simple, consistent, and expressive syntax, well-suited to object-oriented programming (everything is an object, which enforces simplicity). excels in one-liner scripts, command-line utilities, string processing, and metaprogramming. uses the nicer @, @@, and $ prefixes for instance, class, and global variables. provides p/pp for debugging. designed as a successor to python, perl, and smalltalk. scope is marked by begin/end rather than indentation, and ambiguously also with {} where no additional brackets are required for additional method calls on the result. has ambiguous symbol syntax (:k => v vs k: v).

  • sescript: javascript written with s-expressions. compiled output closely resembles hand-written javascript, combining scheme-like syntax with javascript functionality.
  • posix shell: relies heavily on calling external programs for tasks that other languages handle with built-ins, which is both a strength and a weakness. while flexible, it is processing-expensive and lacks the structural consistency expected of a full-fledged programming language.

tier 3

  • python: similar to ruby but older. pros: indent-based syntax, good selection of scientific libraries, docstring placement, hash comments, and optional type hints. cons: more irregularities, exceptional elements, and syntactic limits (e.g., self, underscore prefixes). not everything is an object, and printing is less elegant than in ruby. overall, pythons may be slightly less well-rounded than ruby.
  • clojure: closer to common-lisp than scheme. gets many things right, such as hashtable literals and favoring immutable data structures. runs on the java virtual machine (JVM), offering access to the entire java library ecosystem. licensed under the eclipse public license.
  • php: large, useful integrated library. since version 7, it has gained speed. however, its syntax is irregular (dollar signs for variables, noisy with semicolons, backslashes in namespaces). lacks good first-class functions and requires isset checks for hash entries, with many implicit type conversions. often requires more comments than code. javascript can often provide more elegant solutions. see phpsadness for a detailed critique.

not recommended

  • bash: while shell is highly useful on many command-lines and fine for small scripts, anything larger than 30 lines quickly devolves into a cryptic mess. formatting is inconsistent, and built-in keywords and binding names (e.g., if-fi, variable substitution, quoting) are often meaningless or chaotic. posix shell is preferable for portability over bash/zsh.
  • visual basic: largely superseded by languages like ruby. suffers from historical misfeatures and missing features (e.g., error handling, characters). rubys syntax bears some similarities, but is generally more coherent.
  • common-lisp: bloated compared to scheme. its design and naming scheme are inconsistent and arbitrary (e.g., defun, defvar, progn), with a less readable use of english words.
  • java: notorious for its business bloat. often taught in schools, it specializes in object-orientation (everything must be a class). its verbosity, repetitive patterns, and reliance on uppercase and camelCase hinder readability. though it has a decently optimized JVM, java code often contains more comments than actual logic. third-party libraries mirror javas core—numerous, but rarely satisfactory—forcing developers to rewrite basic components frequently due to the lack of abstraction features.
  • perl: eccentric, with context-sensitive expressions, dynamic scoping, various variable prefixes, and a proliferation of operators. while the syntax is relatively forgiving, the code remains odd. its dynamic nature, including the infamous 1; at the end of modules, makes perl esoteric. bash shares some of its oddities. try installing perl modules locally like node_modules. everything perl is praised for can usually be done better in ruby. despite this, the perl community remains strong, with numerous well-maintained modules.
  • c++: not a successor to c. most c++ extensions (e.g., object-orientation, exception handling) are overcomplicated and unnecessary. implicit type conversions and helper libraries lend it the appearance of a higher-level language, but the complexity of templates and the stl often outweigh their benefits. while hierarchical types (e.g., inheritance) are cited as strengths, c++ tends to introduce more problems than it solves.
  • c#: non-free and developed by microsoft, c# targets the proprietary windows platform. it emphasizes object-orientation, functioning more like java than c. deceptive marketing often leads people to believe c# is an official successor to c, causing confusion. the lack of an open-source runtime further entrenches it in microsofts ecosystem.
  • objective-c: proprietary to apple and explicitly designed for ios and osx. its syntax is unusual but features extensive use of keyword arguments, a positive aspect. however, it remains tied to apples closed ecosystem.
  • swift: the successor to objective-c, inheriting the same proprietary platform issues. despite improvements, swift is still confined to apple’s ecosystem.
  • haskell: dubbed "functional perl" because of its syntax, haskell is highly mathematical in nature but difficult to intuit, with cryptic syntax and numerous symbols. it’s more functional than most languages, but lazily typed, which isn’t always beneficial. its enormous install size (over 700MB) and cryptic syntax detract from usability. haskells package manager is notoriously slow and prone to corruption. while its academic foundation gives it an edge over many languages, it remains difficult to work with in practice, and i do not want to imagine the complexity of writing parsers for it.

neutral

  • lua: primarily used as an extension language within larger programs. similar to ruby but with fewer features. small and lightweight, making it easy to integrate into various applications.
  • scala: combines functional programming features with c-style syntax. built on the java virtual machine, similar to clojure, providing access to java libraries while offering more functional capabilities.
  • emacs-lisp: only functions within the emacs environment. while not bad, it would be preferable if it were based on guile or scheme, which would enhance its consistency and flexibility.

other

  • go: features garbage collection and cross-platform compilation. lacks exceptions, instead handling errors through multiple return values, which can be cumbersome but enforces explicit error management.
  • rust: a low-level language without garbage collection, emphasizing memory safety through explicit ownership semantics. suited for embedded programming, it compiles directly to machine code without a runtime. it offers concise type names, hygienic macros, 128-bit types, and requires semicolons for every expression. rust is often seen as more in line with the complexity and goals of c++, but with cleaner syntax and stronger safety guarantees.