Test
Language constructs
There are a variety of constructed abstractions in a language that not only give it logical/expressive power, but help guide and define the user-friendlyness of the language. A good example of this is Rust's lifetime and borrowing rules. While these rules make it possible to write and express programs that would be difficult to keep track of in a language like C, it also steepens the language's learning curve.
In designing Wright, I want to make a wide variety of language constructs available to the user to help make the language more elegant, without making it too much more difficult to use. In designing these language constructs, a few principles should be kept in mind.
- Wright aims to be a relatively simple, easy to use language.
- Wright aims to protect the user, to the greatest extent possible, from writing buggy code.
- Wright aims to show an appropriate amount of the language's internals. Users should be able to reason about how their code runs and what allocates, or doesn't.
- Wright is a multipurpose programming language. No one paradigm or style should be expressly promoted over others.
With those principles in mind, we can start to establish a set of features to guide the language's design.
- Wright is strongly typed, and will infer types wherever possible.
Wright is garbage collected.-- I changed my mind on this -- Wright will have lifetimes and borrowing similar to Rust.- Wright has traits.
- Wright has enumerations.
- Wright has tagged unions.
- Wright has
classesrecord types. - Wright has type aliases.
- Wright has constraints (to be discussed further).
- Wright has inheritance for traits, enumerations, tagged unions, and constraints.
- Functions are first class language constructs in Wright.
- Wright does not have macros -- most macro-style meta programming should be achievable with generics.
- Wright has abstract types -- representation & implementation can be dependent on the generic used.
On Constraints:
Wright will be one of the few multipurpose languages that I know of to use constraints. Constraints can be a very
powerful tool for logical induction. They allow a programmer to define and check parts of their program at compile time.
Wright constraints will be invokable both at compile time and at runtime. There may be some exceptions if we ever decide
to allow definition of compile-time only (const constraint
) constraints. Constraints will be strongly bound to a type,
but that type may be generic (so constraints on lists and arrays will be possible). Constraints will act very similarly
to functions, carrying zero sense of state or instantiation like a class might.
Note
This document is a work in progress, and may be changed or updated further at a later date.
User defined optimizations
One of the hardest things for me to reconcile as I build this language is how to make it high-level, while still providing the ability to do relatively low-level things. I would make it completely low-level, however Rust already exists as a well-liked, mature, production-ready, memory-safe language with many of the same features I hope to build into Wright. Building Wright as another low-level language with a borrow checker and functional programming elements would not only make it completely derivative of Rust, but also introduce many of the same drawbacks that Rust has in terms of expressing Futures & other complex memory-related types and in terms of learning-curve (especially around the borrow checker).
In order to do both, the vast majority of programming in wright will be covered under a garbage collector. Programmers will write classes, enums, and unions, without ever thinking too hard about memory allocation or management.
... TBD
For many languages, threading can be a point of tension. When to use it (especially now that single-threaded async is more common), how to use it, and how to optimize it are all common issues.
In building wright, I decided it would be best to separate async and syncronous code/threads to avoid unnecessarily compiling/linking/running an async runtime to manage futures.
The Backend(s) of the Wright Compiler and Interpreter.
I have had a many thoughts, opinions, and different stances on what I wanted to build for Wright's backend. I have changed my mind more times than I can count, and I'm certain I will continue to change my mind on this several more times.
So far it appears there are a few main target options:
LLVM | Cranelift | JVM / Java Bytecode | Bespoke bytecode compiler & interpreter | Bespoke bytecode compiler & transpiler | |
---|---|---|---|---|---|
Output | Machine code | Machine code | .class file | Custom bytecode | Custom bytecode & transpiler targets |
Targets | vey many | x86_64 , aarch64 (ARM64), s390x (IBM Z), riscv64 | JVM | Anything that the rust based interpreter can run on | very many (assuming transpile to LLVM) |
Right now I'm largely tempted to target both a bespoke bytecode interpreter (perhaps in addition to a transpiler) and LLVM. I like the idea of compiling to Cranelift as well, but the additional work for it may be more than it's worth. Compiling to the JVM would be cool for interoperability with Java/Scala/Kotlin/etc programs, but my language is so different from them that there would be a significant amount lost in translation from Wright to the JVM. I will start with the bespoke interpreter/transpiler.