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.