Dyon is a scripting language that I am working on, and has now reached version 0.8.

Yeeehaaaaaa!

In the last blog post I wrote a summary of v0.7 and about the upcoming features in v0.8.

Also, I promised this release to be awesome.

But first, some important information for new people based on the feedback from previous post:

Why Dyon is NOT like other languages

Dyon does not have a garbage collector, and does not have ownership semantics.
It uses a lifetime checker, simpler, but less powerful than Rust.

foo(a: 'b, b) { // `a` outlives `b`
    ...
}

This means that Dyon has an extremely limited memory model compared to most languages.
The whole language semantics and syntax is designed to work around this limitation:

  • Atomic reference counting of arrays and objects
  • Copy-on-write for mutable aliased memory
  • Mathematical loops using index notation

The number one issue for some people responding to last blog post, was the lack of iterators.
Using iterators in Dyon is problematic because it leads to copy-on-write.

There is also another reason why iterators are not the focus in this language:

Mathematical loops works better with data-oriented patterns!

A consequence of the memory model is that object-oriented patterns does not work at all. For example, if you try to mutate an object by calling a method:

fn main() {
    list := [{name: "John"}]
    change_name(mut list[0], "Peter")
    println(list[0].name) // prints `John`.
}

fn change_name(mut person: {}, name: str) {
    person.name = clone(name)
}

This prints out “John” instead “Peter”. The reference counter of the item in the list increases, which leads to copy-on-write when changing it.

Therefore, you can not use object-oriented programming AT ALL in Dyon.
Instead, Dyon uses data-oriented patterns, which basically means:

Put stuff in flat arrays and iterate over them.

To make this convenient, Dyon uses current objects and mathematical loops:

fn main() {
    ~ list := [{name: "John"}] // Make `list` a current object.
    foo()
    println(list[0].name) // Prints `Peter`.
}

fn foo() {
    update_names()
}

fn update_names() ~ mut list: [] { // Bring back `list` into lexical scope
    for i { // Infer range from loop body.
        list[i].name = "Peter"
    }
}

When you are iterating over an array of arrays of arrays (3D), you can write:

for i, j, k {
    list[i][j][k] = ...
}

Instead of iterators, Dyon has many loops for common iterator functionality:

  • any, e.g. a := any i { list[i] > 2 }
  • all, e.g. a := all i { list[i] > 2 }
  • min, e.g. a := min i { list[i] }
  • max, e.g. a := max i { list[i] }
  • sift, creates a new list e.g. a := sift i { list[i] * 2 }
  • sum, e.g. a := sum i { list[i] }
  • prod, e.g. a := prod i { list[i] }

You can use continue and break with these loops to do filtering.

Once you have such loops, it makes sense to add secrets:

fn main() {
    list := [1, 2, 3]
    a := any i { list[i] == 2 }
    if a {
        println(why(a)) // prints `[1]`, because it is the indices of why `list[i] == 2`
    }
}

A secret is meta data for bool and f64 that returns the indices of the loops making up the answer.
It works with any combination of any, all, min and max loops.

Some people, who might be more familiar with mathematics, love this.

To the others who do not like it:

Using Dyon helped me understand mathematics better, so perhaps it could help you too?

Anyway, that is how the language works at the moment, so now you know there is for a good reason.

My experiences with Dyon so far

This was not supposed to be serious blog post, but for celebrating the release!
I wanted this to be a special moment, because I have worked so long and finally thinking “I have made it!”.
Therefore, I will share my thoughts with you about Dyon in a way that is more like a party speech:

When I released v0.7, I thought about Dyon as scripting language that was better in some ways,
than my experiences with other scripting languages.
Incomplete, full of bugs, but promising.

Now, I think it is simply the best scripting language I have ever used.

Do not misunderstand me, when I say “best scripting language” I mean just that.
I do not mean “best programming language”.
There are lots of stuff Dyon can not do, which Rust is a better language for.

However, when it comes to scripting, I mean, “scripting” as in scripting,
or how should I put it …

… if you imagine a landscape with elevation proportional to “scripting experience”, then I think Dyon is Mount Everest.

It is far from perfect, but Mount Everest is far from perfect either, it just …

… is a total mountain feeling to that scripting experience, if you understand what I mean?

Tomorrow some people will try it out and not like it,
because they have not grown into Dyon like I did,
but I do not think it matters.

I do not know when it will reach 1.0, but it feels like if there is a moment where Dyon went from being “almost something” to “something”, then I think it is this release.

Happy birthday Dyon! Let us celebrate!

One more thing …

Since the last blog post, I added some new features.

Dyon got closures:

fn main() {
    a := \(x) = x + 1
    println("0." + str(\a(7))) // prints `0.8`
}

You can use grab to capture a value from the closure environment:

fn main() {
    name := "Dyon"
    a := \(x) = (grab name) + " 0." + str(x)
    println(\a(8)) // prints `Dyon 0.8`
}

A grab expression is read-only, because it is a partial evaluation operator.
It computes a value, and injects it into the AST.
This means you can improve performance by doing expensive computations when creating the closure.

You can print out closures to see what they do:

fn main() {
    name := "Dyon"
    a := \(x) = (grab name) + " 0." + str(x)
    println(a) // prints `\(x: any) = "Dyon" + " 0." + str(x)`
}

Oh, we can improve the performance of this closure!

fn main() {
    name := "Dyon"
    a := \(x) = (grab name + " 0.") + str(x) // Put strings together when creating the closure.
    println(a) // prints `\(x: any) = "Dyon 0." + str(x)`
}

To grab values from higher closer environments, use grab 'n <expr>. For example, here is a function that takes two functors and joins them together to one functor:

fn fjoin(
    a: \(\(any) -> any, any) -> any,
    b: \(\(any) -> any, any) -> any
) -> \(\(any) -> any, any) -> any {
    return \(f, v) = {
        a := grab a
        \a(\(v) = {
            b := grab '2 b
            \b(grab f, v)
        }, v)
    }
}

Check out the functor example to see more.

Closures must use current objects to mutate the environment.

The benefit of this design:

  • closures do not require a lifetime
  • can be returned from functions
  • easy to refactor to a function or vice versa