2025-08-04

opinions on programming languages

language ranking

while the following list does not go far beyond opinion, and opinions adapt over time, i only rate languages which i have used 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 compatible with the javascript ecosystem, including node.js. coffeescript + javascript -> agility. indentation-based syntax ensures readability and coherence. coffeescripts expressiveness comes from well-recognizable patterns derived from a minimal set of constructs. 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 reduces cognitive load.
  • schemes formally specified standards (eg, r5rs) contrast with the ad-hoc evolution seen in many languages.
  • i find scheme allows for faster and less error-prone development.

criticisms

  • certain r6rs features are questionable:

    • inconsistent naming schemes (e.g., flonums with "fl" prefix).
    • allowing square bracket as alternative to round brackets. it adds syntactic ambiguity and hinders code editing.
    • module system indentation adds unnecessary verbosity.
    • file extensions (.sls, .ss) reflect file purpose rather than syntax or processor.
  • i dislike the traditional indentation style. regular indent with one step per increased nesting depth better conveys complexity and context.
  • the use of doubled semicolons ;; and ;;; for comments feels redundant.
  • guile scripts require two lines instead of one line for shebangs.
  • scheme suffers from a lack of unity: multiple implementations, varied module systems, complicated indentation rules, and bracket ambiguities detract from cohesion.
  • naming conventions like "cons" and "cdr" feel cumbersome.
  • i struggle with the quote syntax ('`,).
  • difficult to edit in basic editors on the command-line. for example, on a remote server.
  • deep nesting might not be as easily skimmable 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 or string processing for system scripts is not as easy without importing several additional modules, and even then, using shell/ruby/node would often result in more straightforward code.

ruby

  • relatively simple, consistent, and expressive syntax
  • everything is an object. 1.to_s
  • excels in one-liner scripts, command-line utilities, string processing, metaprogramming
  • variable prefixes: @ for instance variables, @@ for class variables, $ only for globals
  • designed as a successor to python, perl, and smalltalk, inspired by lisp
  • provides p/pp for debug printing
  • scope marked by begin/end keywords rather than indentation
  • also uses {} for scope when chaining method calls. this is unfortunately otherwise ambiguous with begin/end
  • multiple alternative symbol syntax: :k => v vs k: v
  • requires utf-8 encoding comment at top of file, which isnt great
  • promoted from tier 2 to tier 1 because it does so well as an alternative to shell for complex scripts. often almost like a drop-in replacement when shell becomes unwieldy

tier 2

javascript

  • expressive but syntactically noisy
  • semicolons optional but often used
  • mixes square, curly, and round brackets
  • offers many ways to express similar constructs: object literals, constructor functions, classes
  • asynchronous handling can be hard to grasp: async/await, promises, continuation-passing style
  • pitfalls: implicit type conversions, truthy/falsy values, accidental globals (without var)
  • first-class functions and closures remind of lambda in scheme
  • malleable and pragmatically useful despite quirks
  • never versions introduced the noisy const keyword and an awkward attempt at a module system
  • node.js for system scripting: fast startup, useful libraries (e.g., commander.js). risk of: bloated scripts via package.json, sometimes required use of async complexity

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.

sescript

javascript written with s-expressions. compiled output closely resembles hand-written javascript, combining scheme-like syntax with javascript functionality.

tier 3

python

  • similar interpretive and scripting heritage than ruby but older
  • pros:

    • indent-based syntax for block structure
    • extensive, mature scientific and data libraries (eg, numpy, pandas, matplotlib)
    • docstrings as first-class documentation
    • # for line comments
    • optional static type hints (pep 484+)
  • cons:

    • more syntactic irregularities and exceptions
    • awkward constructs:

      • explicit self in method definitions
      • underscore conventions for access and naming
    • not everything is an object (eg, primitive None, control keywords)
    • less elegant i/o syntax than ruby (print() vs p/pp)
  • ubiquitous in scientific computing, automation, build systems, ml, and tooling
  • under conditions of no path dependence and perfect information, users of python would likely overwhelmingly switch to ruby for general-purpose scripting and application
  • pythons comparative advantages are domain-specific:

    • massive scientific/data ecosystem

    • institutional inertia

    • availability on nearly all systems by default

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 short type names, hygienic macros, 128-bit types, and mandatory semicolons after expressions.
  • rust is often aligned with the complexity and objectives of c++, for better or worse, 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.
  • c# is another popular game development language, however, being ill suited to real-time systems, no major studio would choose c# for core engine development even if transition were costless. many would likely choose rust over c++ given equal ecosystem maturity and toolchain support

php

  • large, integrated standard library
  • performance improved significantly since version 7
  • syntax issues:

    • dollar signs for variables
    • excessive semicolon usage
    • backslashes in namespaces
  • associative array access:

    • requires isset checks
    • ?? operator mitigates some cases
  • pervasive implicit type conversions
  • no native support for:

    • asynchronous processing
    • parallel execution
  • typical execution model:

    • interpreter starts per request
    • runs and exits (stateless)
  • heavily patched syntax:

    • gradual improvements to resemble better languages
    • cannot overcome foundational design flaws
  • see: phpsadness for additional critique

not recommended

bash

  • suitable only for minimal scripts (<30 lines)
  • formatting inconsistent; keyword pairs (if-fi) unintuitive
  • quoting and substitution semantics chaotic
  • variable handling obscure and error-prone
  • suffers from feature creep in bash/zsh variants
  • posix shell preferred strictly for portability and discipline

visual basic

  • largely obsolete; supplanted by cleaner scripting languages like ruby
  • missing core features (e.g., structured error handling, consistent char types)
  • historical baggage and awkward constructs
  • ruby achieves similar readability goals with vastly more coherence

common-lisp

  • bloated and baroque compared to scheme
  • inconsistent naming conventions (defun, defvar, progn)
  • excessive use of english words over symbolic economy
  • lacks the conceptual elegance and syntactic economy of scheme

java

  • canonical enterprise bloat
  • enforces object-orientation even when inappropriate
  • verbosity and boilerplate (getters/setters, public static void) obfuscate intent
  • widespread reliance on camelcase and capitalized types reduces readability
  • code-to-comment ratio often inverted
  • third-party libraries mirror core: verbose, fractured, unsatisfying
  • frequent reinvention due to weak abstraction primitives

perl

  • eccentric, idiosyncratic, and context-sensitive
  • dynamic scoping, sigil proliferation ($, @, %)
  • postfix oddities (1; at eof) and symbolic idioms ($_, <>, =>)
  • dozens of infix operators (<=>, //=, ~~, etc.)
  • modules not easily shippable with code like node_modules
  • ruby does everything perl aimed to do cleaner and better
  • only redeeming feature: curated cpan modules

c++

  • not a proper successor to c; an accreted mess
  • object orientation and exceptions introduce needless complexity
  • templates and stl often obfuscate more than they help
  • implicit conversions and operator overloading cause silent failure
  • hierarchical typing adds structure at the cost of clarity
  • encapsulation is the only substantive improvement over c, but poorly integrated

c#

  • not free; bound to microsoft's ecosystem
  • markets itself misleadingly as a "c successor"
  • official runtime non-portable and historically closed
  • inherits verbosity and object-orientation dogma from java
  • minimal community presence outside .net and windows

objective-c

  • proprietary to apple platforms
  • syntax features keyword-rich, message-passing style
  • expressive method signatures improve clarity in many cases
  • uses idiosyncratic delimiters ([obj message]), but consistent once learned
  • primary limitation is ecosystem lock-in to osx and ios
  • legacy burden from tight platform coupling, not intrinsic language flaws

swift

  • apple's replacement for objective-c
  • retains all the ecosystem constraints
  • syntax superficially improved, but tools and libraries remain apple-bound
  • unsuited for cross-platform or systems programming

haskell

  • "functional perl". cryptic, symbolic, and opaque
  • non-strict (lazy) evaluation enables powerful abstractions, but introduces indirection
  • error messages arcane; types abstract to a fault
  • ghc install >700mb; tooling is bloated and fragile
  • package manager (cabal/stack) slow and easily corrupted
  • academically compelling, practically exhausting

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.
  • 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.

go

  • compiled, statically typed, garbage-collected
  • cross-platform by default; produces static binaries
  • concurrency via goroutines and channels (lightweight, csp-inspired)
  • error handling:

    • no exceptions
    • uses multiple return values (if err != nil)
    • explicit but verbose
  • lacks expressive abstraction mechanisms:

    • generics added late, with limited elegance
    • discourages higher-order patterns
  • syntax is simple but rigid; minimal metaprogramming
  • unsuitable for scripting or exploratory programming
  • excels in backend services, devops tooling, and deployment simplicity

clojure

  • closer to common lisp than scheme in syntax and semantics
  • strengths:

    • favors immutable data structures by default
    • provides hashtable and vector literals
    • strong concurrency primitives (e.g., stm, atoms, agents)
    • repl-driven development model
    • full access to the java ecosystem via jvm interop
  • drawbacks:

    • jvm dependency incurs memory overhead and slow startup
    • unsuitable for minimal or script-like workflows
    • syntax cluttered by reader macros and metadata
  • licensed under the eclipse public license

general thoughts

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.

on the term "readability"

readability has often been 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.