A Demo of qdbp
Functions
The equivalent of
def add(a, b):
return a + b
add(1, 2)
in qdbp is
add := {a b | a + b.}
add! a: 1 b: 2.
add is technically not a function. It is a prototype with a single method !. For convenience, however, we refer to any prototype with a ! method as a function.
Generics
Methods are generic by default. Here is a generic print function:
print := {that | that Print.}
_ := print! 3.
print! "hello".
If/Then/Else
if := {that then else|
that
True? [then!.]
False? [else!.].
}
if! 1 > 2.
then: {
"true" Print.
}
else: {
"false" Print.
}.
Alternatively, we can implement If as a method in a boolean object like this:
true := {
BoolVal[#True{}]
If[then else|
this BoolVal.
True? [then!.]
False? [else!.].
]
}
true If
then: {
"true" Print.
}
else: {
"false" Print.
}.
Switch
There are a variety of ways to implement switch depending on your needs. Here is one
switch := {that |
{
Val[that]
Result[#None{}]
Case[that then|
this Val. = that.
True? [
result := then!.
{this Result[#Some result]}]
False? [this].
]
Default[then|
this Result.
Some? [that| that]
None? [then!.].
]
}
}
str := switch! 5.
Case 1 then: {"one"}.
Case 2 then: {"two"}.
Case 3 then: {"three"}.
Case 4 then: {"four"}.
Case 5 then: {"five"}.
Default then: {"None of the above"}.
str Print.
Data Structures
All of the data structures here can be implemented in qdbp. In addition, qdbp will have Perceus Reference Counting in the near future, allowing data structures to reuse memory when possible and claw back some of the performance lost to immutability.
As an example, here is an implementation of a stack:
stack := {
Data[#Empty{}]
Push[that|
curr_data := this Data.
{ this
Data[#NonEmpty {
Val[that]
Next[curr_data]
}]
}
]
Peek[
this Data.
Empty?[ABORT.]
NonEmpty?[data| data Val.].
]
}
stack Push 3. Push 2. Peek. Print.
Operators
true := {
BoolVal[#True{}]
&&[that|
this BoolVal.
True? [that]
False? [this].
]
}
false := {true BoolVal[#False{}]}
true && false.
While Loops
while := {that body|
that!.
True? [
_ := body!.
this! that: that body: body.
]
False? [{}].
}
; Will loop infinitely
while! {1 < 2.}
body: {
"hello world" Print.
}.
Functional List Manipulation
For brevity, the implementation of the list objects is omitted.
double_list := {list | list Map {that | that * 2.}. }
sum_list := {list |
list FoldLeft fn: {that acc | that + acc.} initial: 0.
}
{}
Classes
This python code:
class circle:
def __init__(this, radius):
this.radius = radius
def print(this):
print(this.radius)
my_circle = circle(3)
my_circle.print()
has this
make_circle := {radius |
{
Radius[radius]
Print[this Radius. Print.]
}
}
my_circle := make_circle! radius: 3.
my_circle Print.
as its qdbp counterpart. Class-like behavior can be mimicked with prototypes.
Single inheritance/Dynamic dispatch
Some cases of single inheritance can be mimicked with prototype extension.
basic_circle := {
Radius[3]
Print[this Radius. Print.]
}
colored_circle := { basic_circle
Color["red"]
Print[
_ := this Color. Print.
this Radius. Print.
]
}
_ := basic_circle Print.
colored_circle Print.
Partial Objects
Consider the following object
comparison := {
>=[that|
this < that.
False? [#True{}]
True? [#False{}].
]
<=[that|
this > that.
False? [#True{}]
True? [#False{}].
]
=[that|
this >= that.
True? [this <= that.]
False? [#False{}].
]
!=[that|
this = that.
True? [#False{}]
False? [#True{}].
]
}
Now, any object that extends comparison and implements < and > gets the other comparison operators for free.
For loop with iterator
for := {iter body|
iter Data.
None? [{}]
Some?[data|
_ := body! (data Val).
this! iter: data Next. body: body.
].
}
from := {that to|
{
Start[that]
End[to]
Val[this Start.]
Next[
start := (this Start) + 1.
{ this
Start[start]
}
]
Data[
(this Start) <= (this End).
True? [ #Some this ]
False? [#None{}].
]
}
}
for!
iter: (from! 1 to: 10)
body: {that | that Print.}.
Phantom Fields
Some values may have the same types but mean different things. For example, an int could mean a number of cookies or it could mean a day of the month. When we perform operations, we can make sure we don't use the wrong values at compile time by using phantom fields.
day_12_of_month := { 12
DayOfMonthUnit[{}]
}
five_cookies := { 5
CookieUnit[{}]
}
fn_involving_days := {that |
_ := {that DayOfMonthUnit.} ; compiletime check for unit
; Do something with `that`
...
}
_ := fn_involving_days! day_12_of_month. ; ok
fn_involving_days! five_cookies. ; compilation error
Error handling
Returning Errors
safe_divide := {a b|
b = 0.
True? [#Error{}]
False? [#Ok a / b.].
}
result := safe_divide! a: 1 b: 0.
result
Error? ["error" Print.]
Ok?[that| that Print.].
Propagating Errors
error := {
Transform[fn|
this Val.
Error? [err| this]
Ok? [that| {this Val[#Ok fn! that.]}].
]
}
safe_divide := {a b|
b = 0.
True? [{error Val[#Error{}]}]
False? [{error Val[#Ok a / b.]}].
}
safe_divide_6 := {a b c d e f|
(safe_divide! a: a b: b)
Transform fn: {that | that / c.}.
Transform fn: {that | that / d.}.
Transform fn: {that | that / e.}.
Transform fn: {that | that / f.}.
}
safe_divide_6! a: 3996 b: 3 c: 1 d: 2 e: 2 f: 1. Val.
Error? ["Error" Print.]
Ok? [that | that Print.].
Abort
panic := {that |
_ := that Print.
ABORT.
}
safe_divide := {a b|
b = 0.
True? [panic! "divide by 0".]
False? [a / b.].
}
_ := safe_divide! a: 1 b: 0.
"Shouldn't get here!!!" Print.
Infinite Lists
pows_of_2_list := {
Val[1]
Next[
cur_val := this Val.
{ this Val[cur_val * 2.] }
]
}
_ := pows_of_2_list Val. Print.
pows_of_2_list := pows_of_2_list Next.
_ := pows_of_2_list Val. Print.
pows_of_2_list := pows_of_2_list Next.
_ := pows_of_2_list Val. Print.
pows_of_2_list := pows_of_2_list Next.
pows_of_2_list Val. Print.
Modules
math.qdbp:
{
Factorial[that|
that = 0.
True? [1]
False? [that * (this Factorial (that - 1)).].
]
Abs[that|
that < 0.
True? [-1 * that.]
False? [that].
]
}
Some other file in the same directory as math.qdbp:
math := @math
_ := math Factorial 5. Print.
math Abs -3. Print.
Defer
defer := {that body|
result := body!.
_ := that!.
result
}
defer! {"finished doing fancy math" Print.}
body:
{
_ := "doing fancy math" Print.
1 + 1. + 3. * 15.
}.
This can be used, for example, to handle file closing or for automatic benchmarking
Domain Specific Language Creation
We can create our own DSLs in qdbp despite it not having macros. For example, here is sample usage of a DSL for a build system(implementation of the DSL not shown)
my_project
AddLibDirectory "./lib".
AddTestDirectory "./test".
AddExecutable "bin/main.qdbp".
SetOptimizationLevels
performance: 3
size: 2.