Design Rationale

Method Invocation Syntax

The method invocation syntax, inspired by smalltalk, is qdbp's biggest syntactic variation compared to other languages. While most languages have

foo.Bar(a, b)

qdbp has

foo Bar arg1: a arg2: b.

There are a few reasons for this. 

Readability

In general, qdbp prioritizes simplicity of reading over simplicity of writing. Forcing users to specify argument names makes it easier to read and understand their code. Consider the following two snippets:

rectangle Make width: 3 height: 4.

and

rectangle.Make(3, 4)

In the first example, we can clearly see what the 3 and 4 mean, whereas in the second example, we have to look at the definition of rectangle.Make to understand what the arguments mean.

Overloading

Overloading by type is tricky to implement and reason about in a language like qdbp with a structural type system. Overloading by parameter name, which is only possible with named arguments, is much easier. Having and overloading on named arguments allows us to write code like this:

circle1 := circle Make radius: 3.

circle2 := circle Make area: 15.

circle3 := circle Make diameter: 4.

Operators

Dedicated operator syntax often leads to programmers having to memorize a large number of precedence and associativity rules, introducing a lot of mental overhead. As a result, qdbp does not have operators. However, operator-like syntax like below is natural and unambiguous in qdbp because of its method invocation syntax. 

(3 + 4) * 2. / 18.

"Extensibility"

qdbp is not extensible. It has no macros or facilities for compile-time metaprogramming. However, its method invocation syntax makes the language feel extensible. For example, we can implement if/then/else as a prototype

if := {condition then else|

  condition

    True? [then!.]

    False? [else!.].

}

and use it like so

if! condition: ...

  then: {

    ...

  }

  else: {

    ...

  }.

Similarly while, for, switch, monads, iterators, defer, etc can all be implemented as objects and used naturally. We provide some examples of this here.

The variety of constructs that the syntax makes possible and natural to implement extends beyond normal general purpose language features. qdbp's syntax makes domain specific language(dsl) implementation easy. For example, there currently is a work in progress dsl for a build system using qdbp that looks like this:

my_project

  AddLibDirectory "./lib".

  AddTestDirectory "./test".

  AddExecutable "bin/main.qdbp".

  SetOptimizationLevels performance: 3 size: 2.

Users of this build system don't need to learn a new syntax and developers don't need to implement a parser or macros - it can all be done naturally in qdbp. 

., @, #, and ?

These symbols were all picked because their linguistic meaning corresponds to their meaning in qdbp.


[]

We chose

MethodName[arg1 arg2 ...| body]

as opposed to

MethodName(arg1, arg2, ...) {body}

because the former is simpler and more concise.

The syntactic similarity between tagged object pattern matching expressions and field methods is intentional. Tagged objects are the dual of prototypes; tagged objects express a "this or this" relationship while prototypes express a "this and this" one. Their syntax similarities reflect this duality.  

this and that

qdbp has two keywords. One is the name of an implicit method parameter whose value is the prototype that holds the method. The other is a parameter name that, when used as the first parameter, can be omitted on method invocation. Both are syntax sugars that eliminate boilerplate but, technically, aren't strictly necessary. They just make recursion, operators and many object oriented patterns more easily expressible. These keywords could have easily been named differently - self/other, this/val, etc. We arrived on this and that for a few reasons

Structural Type System

qdbp uses a simple yet powerful kind of type system that is often referred to as "static duck typing" or "row polymorphism." The type system(described in detail here) is very similar to Python's in that the type of an object is determined by the fields that it has rather than the name of a type. qdbp's type system allows for

The Lack Of

Many of qdbp's design decisions were made using the following principle: If a construct can be emulated with another existing feature, it should not be added to the language. The converse, A construct can be added to the language if it cannot be emulated with another existing feature, was also used to guide design decisions.

switch, for, while, if, defer...

Because all of these constructs are naturally implementable as libraries as shown here, in keeping with the guiding principle above, they are not language primitives. 

Non-method fields

Unlike other languages, all fields in qdbp prototypes are methods. There are no "regular data" fields. This is because "regular data" fields are trivially emulatable with methods that just return the data.

Classes

Classes can be emulated as functions that return prototypes, removing the need to add them separately

Inheritance

Inheritance introduces a lot of complexity to the type system. It requires subtyping, and inference of subtypes is notoriously difficult to theoretically impossible. Instead, prototype extension is much simpler and captures most of the uses of single inheritance. qdbp has no way of mimicking multiple inheritance, but multiple inheritance is controversial at best and often unnecessary.

Mutability

Mutable variables introduce complexity, from the syntactic complexity of adding an assignment syntax to the semantic complexity of having to reason about variables whose values are constantly changing. qdbp avoids this by not having mutable variables. As an added bonus, this makes it impossible to form circular references, allowing qdbp to have predictable, reliable reference counting. This does have unavoidable performance implications, but for most applications, the performance hit is more than tolerable.

Macros

While macros are a powerful tool, they are also a source of complexity. They often make programs impossible to reason about and can be a source of subtle bugs. qdbp does not have macros, but it does have a powerful, expressive syntax that makes it easy to use objects for many of the same purposes as macros.

Non-Local Control Flow(exceptions, algebraic effects, call/cc, etc)

Non-local control flow often makes programs much harder to reason about and adds a lot of complexity to languages. In addition, it is much trickier to implement and often requires significantly more advanced type systems than local control flow does. As a result, qdbp does not have any non-local control flow(other than ABORT.). 

Purely Functional I/O

Purely functional I/O, both in the form of algebraic effects and monads, is a powerful tool for reasoning about programs, but purely functional I/O is complex and qdbp's core value of simplicity makes it a poor fit for the language.

Sequence Expressions

qdbp does not have explicit syntactic support for sequence expressions. Instead, sequencing is done in the following manner

_ := expr1

_ := expr2

expr3

Admittedly, this is unsatisfying. The reason qdbp doesn't have sequence expressions yet is because we haven't found a good syntax for them. In particular, we want to avoid ; for expression separators because people find it annoying and it adds too much punctuation to the language(for example, 1+1+2..; just looks ugly). If you have any ideas for this, please let us know :)

Concurrency

qdbp is simple, and concurrency is complex. It will be added in the future, but it is arguably the hardest feature to add while keeping with the ethos of the language, so we are taking our time with it.