Solar wrote:
One of the things I always liked in C++, and very much disliked in a great many other languages, is this whole "getting into a mindset" thing.
The mindset I'm referring to specifically is the optimization of things that aren't bottlenecks. It creates unnecessary complexity and pidgeon-holes the ability to make future changes.
Solar wrote:
I understand why people would want an easy-to-learn language that makes it difficult to make silly mistakes.
The problems arise when you're past the "silly" stage and find that the language ties one hand behind your back. Or, worse, you get past the "silly" stage and don't realize one hand is tied behind your back.
As Bjarne Stroustrup put it, "C++ is not aspiring to be a beginner-friendly language, but an expert-friendly language".
I don't say that your language is bad if it takes some architectural decisions away from the developer. I am just saying that you should be open about it, and realize that every thing has two sides.
I'll be very straightforward here, this language is *not* being designed for beginners, or to enforce any specific programming paradigm. It's intended to fulfill the same niche as C/C++, but provide richer ways of handling memory, concurrency, code generation, maintenance, and project management. In addition, it also tries to provide a wide set of creature comforts that reduce friction and improve the legibility of the code.
Much like C++, there are values, references, and pointers in the language. Values have pass-by-copy semantics, and are usually primitives like integers and character strings. References are pointers to a single instance of an object; by default, all arrays and structures are passed by reference because the vast majority of the time, that's what you want. Pointers represent unbounded arrays of 0 or more instances of an object. You can override the default passing semantics by adding a sigil of $, &, or * for values, references, and pointers respectively.
Unlike C++, there are no classes. Types represent data, functions operate on data. Uniform Function Call Syntax (UFCS) means that the first operand of a function is used to determine the data type that the function is associated with. This means that you don't have to extend a class to add more behavior to it. Functions are categorized into modules based on what they do, not what type of objects they operate on. The distinction I was making between physical and virtual types is that a majority of the time, you want a function to operate on an actual data structure. But, when you're designing a system that has front-end and back-end subsystems (like a kernel/user interface), you might only want to operate on the interface of that type. This way, if the architecture of the back-end subsystem changes, it won't affect the front-end. Mixing virtual and physical members (by which I mean properties and fields) in a class defeats the entire purpose of encapsulation.
The template syntax of C++ is a nightmare to read, write, and debug. It's uses can primarily be divided into 3 categories: type substitution (generics), code generation, and compile-time execution. I'm tackling these problems directly in different ways: type substitution via pattern matching, code generation via the language's ability to modify it's own syntax tree, and compile-time execution either by using compiler directives or using the compiler as an API (undecided atm).
I'm very much interested in the notion of first-class memory ownership, but as stated, it's a WIP. Concurrency and interprocess communication are another major concern, because C++ completely missed the memo on these things; and they're vital to the scalability of software. I believe it was Joe Armstrong who showed that Erlang could become magnitudes faster than C++ when scaled. And part of that's due simply to the fact that's extremely hard to write concurrent / parallel code in C++.
There are a lot of design decisions weighing heavily on my mind. I have no intention of restricting how people can solve problems. I'm just focused on isolating what the actual problems are and finding the simplest and most elegant solutions to them in a familiar syntax.
EDIT:
Also (this is directed at '~'), I'm already familiar with how compilers work. Front-end stuff is really easy. Just in the past 3 months, I've written over a dozen parsers from scratch in C for various syntaxes. Once you got a syntax tree constructed, you can evaluate it directly. The difference between an interpreter and compiler begins here. You generate a symbol table for the global scope, and for each function, you simplify each instruction down into three-address form; eg. `%0 = %1 + %2`. Typically, single-static assignment is used for all temporaries, so that they're "defined". At this point, you have what is essentially bytecode (like what LLVM has). The hard part is doing optimizations on that bytecode and things like register allocation, because they're often NP-hard problems. If you skip optimization and register allocation (eg. by keeping values on the stack), code generation is as simple as "substitute this sequence of bytecode instructions with this sequence of machine code instructions".