UP | HOME

Programming Languages

There are languages designed by competent academics (SML, Scheme, Miranda, Haskell, Ocaml, F#, Scala3), by talented professionals (Common Lisp, C, Smalltalk, Erlang, Python, Clojure, Go) and by over-ambitious, narcissistic unqualified amateurs (C++, Java), and you can tell.

PHP and Javascript have been “designed” by ignorant (never studied the subject, the principles, and never looked on what decent people did) idiots.

Yes, I know how Stroustrup and Gosling rationalized their decisions (actually - fundamental theoretical fuckups) in retrospect. Both literally ignored everything but Algol, C and Simula. Just basic uniformity and consistency would have eliminated a lot of fundamental problems and pain.

Exactly these fundamental theoretical fuckups are trying to be fixed by Scala, Clojure, Rust (also an amateur crap) and that Google’s take on Rust. They are just trying to apply the results of more than 60 years of PL research, which basically boils down to what Ocaml is.

There are 7 major levels (or contexts) for any programming language: principles, syntax, semantics, idioms, modules, libraries, and tools.

Principles

To understand principles is to understand how and why mathematics works (is). In particular, that arithmetic consist of number types (Sets of numbers) and (together with) a corresponding set of operations (addition, subtraction, multiplication, etc).

It is also important to realize what generalized abstract “structures” (notions) such a Group or a Monoid are.

The main principle in programming languages (just as in Math) is uniformity. It follows naturally from the universal principles of a function- and data- abstractions, which, in turn, is what the Category Theory and the Set Theory are all about respectively.

The Simple Typed Lambda Calculus (has been invented before any digital computers) is an underlying mathematical formalism.

What we call Modularity are just sets of Abstract Data Types, which are named sets of function signatures (exported “public” interface), along with “hidden” (abstracted out by these function signatures) implementation details, packaged into a semi-independent module.

Ideally, such Abstract Data Types (and related modules) should correspond to the major concepts in the problem domain, and to derived generalized abstractions made from analysis and decomposition of the domain.

Last but not least, exact representations of data items (just like 42 or "+" on a sheet of paper) and NOT objects should be passed around (just like “in the wire”), and just like in biology, the notion of an identity of each molecule is redundant (it is defined by its location) while the notion of being exactly the same as any other molecule of its kind is crucial. Our invented Numbers follow the same principles and this is why math “works”.

Uniformity

  • Has the Simple-typed Lambda Calculus at its core (LIPSs and ML descendants)
  • Everything is an expression (not in Java, not in C++. Ocaml has definitions)
  • No implicit coercions (not in Java, not in C++)
  • Every value has a type, even a type-tag attached (LISP and ML descendants)
  • Every expression is reducible to a value, so it has a type (or an Exception)
  • Every value is an expression (in a normal-form - it is self-evaluating).
  • Once created, every value is /immutable.
  • Every /binding is /immutable (a new binding, which shadows, can be introduced).
  • So every compound data-structure is persistent.
  • Referential transparency is maintained or even enforced (Haskell).
  • The proper Numerical Tower is implemented (only Smalltalk and some LISPs, but not Ocaml)
  • Algebraic Data Types and the proper type theory (ML descendants)
  • Uniform (everywhere) pattern-matching on data-constructors (ML descendants)
  • Uniform asynchronous message-passing (Erlang)
  • Single-argument lambdas, currying and partial application (a real uniformity)
  • Syntactic sugar for multiple clauses (with patterns) for a function (SML, Erlang, Ocaml with the function keyword) .

Curried functions have the same signature as logical implications, and this is not a coincidence.

Each argument (value) is captured into a closure (accepted as a “fact”) and a new lambda (closure) is returned for the next argument (“fact”). Any curried function “knows” how many and of which type the arguments must be.

Applying a curried function to a fewer arguments (which returns a lambda for the next argument, not the result) is called partial application.

This is not just “natural”, but also a standard idiom - a partially applied functions can be passed around, carrying already captured arguments with it.

Each clause of a function (defined by pattern-matching) can be considered as a partial function - it accepts a particular subset of a whole type, but is itself a pure function nevertheless.

When a clause of a pattern-matching expression (match ... with ...) introduces new bindings, it is semantically the same as a partial function (of a particular subset of the type): same input - same output. Thus is what we call a uniform pattern-matching, almost like in Erlang (where everything is pattern-matching).

And, of course, pattern-matching is not just a convenient way of destructuring (without throwing exceptions). This is a more general notion of a value to have a certain “shape” (like a molecule) and of matching against a particular shape.

This is what Rust “trannies” missed completely because they never studied the subject, but they are too proud of being “special” and “creative” lmao.

Syntax

Defined by a set of rules, which together specifying what constitutes a well-formed expression.

Syntax-checking is, obviously, done before type-checking and evaluation.

Type declarations

This is the syntax and the rules of how to define new (user-defined) types and especially Algebraic Data Types.

Pattern-matching

Pattern matching on data-constructors (of Algebraic Data Typesp)

Semantics

Basically, semantics are sets of precise rules of how a compiler would “understand” (or an interpreter interpret) a particular kind of expressions (this or that syntactic form). Your understanding must correspond to the compiler’s.

Dynamic semantics

Evaluation rules - how a particular kind of expressions will be evaluated at runtime. For example, an if e1 then e2 else e3 expression has its own evaluation rules (either e2 or e3, depending on what whether e1 is true).

Pattern-matching rules

These can be singled out because they are complex and may or may not introduce local scopes and new bindings.

Static semantics

Typing rules - how a particular kind of expressions will be type-checked by the compiler. For example, an if e1 then e2 else e3 expression has its own typing rules (both clauses e2 and e3 has to be of the same type).

Common idioms

The same computation could be expressed in more than one way. The classic example is how some imperative loops could be expressed as declarative recursive functions, like map or fold.

Idiomatic usage of the comprehension syntax is another way of reducing complexity.

Modules

Exports

We can choose which particular values to export and which to hide.

Imports

This is how we import values from a module (external or internal).

Functors

This defines operations on whole modules which are available in Standard ML, Ocaml and F#.

Libraries

The Standard Library

3rd-party libraries

Internal libraries

Tools

Formatters

Linters

Static analyzers

Interpreters or REPLs

Compilers

Author: <schiptsov@gmail.com>

Email: lngnmn2@yahoo.com

Created: 2023-08-08 Tue 18:40

Emacs 29.1.50 (Org mode 9.7-pre)