2025-06-22

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 understanding, but this impression usually stems from familiarity developed through repeated exposure. reading code involves interpreting visual patterns through multiple cognitive processes, such as returning to the beginning of a line, identifying elements separated by whitespace, and mapping sequences of characters to abstract meanings. human cognitive costs are relatively expensive, and even minor, repetitive tasks can accumulate and lead to cognitive fatigue, reducing the overall work being done.

notation permits a degree of flexibility: formatting can vary without altering the underlying structure. whitespace, often optional, enables a wide range of stylistic choices, typically shaped by each authors personality and conventions.

language ranking

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

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 syntactically 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 - such as easily navigating over and extracting blocks - and macro design. homoiconicity enhances flexibility.
  • 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 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 and less error-prone development compared to other languages.

criticisms

  • certain r6rs features are questionable:

    • inconsistent naming schemes (e.g., flonums with "fl" prefix).
    • allowing square bracket as alternative 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.
  • 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 ('`,).
  • difficult to edit in simple editors on the command-line on a remote server for example.
  • deep nesting might not be as easily skimmed compared to a more imperative series of commands.
  • core and standard library not as pragmatic as, say, ruby or node.js. for example, things like standard input/output string processing for system scripts is not as easy without importing additional modules, and even then, using shell/ruby/node would often result in less code.

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). having to set file encoding to utf-8+ with an extra kind of comment at the beginning of files isnt great.

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, large selection of well-maintained 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, it appears 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 or noisy (dollar signs for variables, noisy with semicolons, backslashes in namespaces). requires isset checks for associative array 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. php is one of those badly designed syntaxes which were patched to improve on things and be more like better languages, however, it can not separate itself from its fundamental flaws.

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 over bash/zsh because of portability.
  • 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: relatively eccentric with its context-sensitive expressions, dynamic scoping, various variable prefixes, and a proliferation of operators. while the syntax is quite 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. its biggest draw are the encapsulation features, where c embarrassingly lacks equivalents. 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 apples ecosystem.
  • haskell: dubbed "functional perl" because of its syntax, haskell is difficult to intuit, with cryptic syntax and numerous symbols. it is more functional than most languages, and lazily typed - which isnt 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.

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 via explicit ownership semantics. suitable for embedded development, it compiles directly to machine code with no runtime. features include concise type names, hygienic macros, 128-bit types, and mandatory semicolons after expressions.

    • rust is often aligned with the complexity and objectives of c++, but with cleaner syntax and stricter safety guarantees. it can thus be regarded as a strong candidate to succeed c++ in game development, where c++ remains dominant.