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.
Much like periods end sentences in the English language, they end method invocation and pattern matching expressions
Similarly, pattern matching can be thought of like asking questions about tagged objects. Hence the ? symbol
When we import a file, we get the expression at(@) the file.
# is used for making tagged objects because # is the hashtag symbol
[]
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
The two parameters are complements of each other, much like the words this and that.
The word this in English is used to refer to the current object at hand, which is exactly what the implicit parameter is.
that in English often refers to an object that is further away and as a result often has to be specifically pointed out. Likewise, the that parameter in qdbp is not implicit - it has to be specified.
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
methods that can be reused with parameters of different types as long as they have the required fields
omission of all type definitions and annotations
different types of objects being able to share field names
the guaranteed absence of runtime type errors
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.