Some observations on implementing programming languages:
If you're going toward the "Js-like" or even total scripting support with JS as the new language, don't. This is a horrible direction, and anyone thats used javascript understands why. If it goes that direction I plan on never purchasing again.
On that note: prototypes have been tried by lua, by javascript, and others. Likewise closures. Prototypes lead to problems with correctness. Likewise closures, or first class scopes, make debugging difficult because it makes flow control more dependent on runtime state, and runtime state tends to be opaque enough.
For languages that did it right, look at Wren, or a less known language, Moonscript. Classes with single inheritance plus mixins for flexibility in behavior.
I second the motion for a spread operator.
ON DATATYPES
As far as classes vs datatypes, I like julias approach to OOP, which is to have a hierarchy of datatypes, and datatype inheritance, rather than objects--while using multidispatch 'methods' instead of interfaces and all other sorts of hacks.
In this way, multidispatch, gains much of the benefits of 1. single inheritance, 2. multiple inheritance, 3. interfaces, 4. abstract base types, 5. composition, and a host of other benefits of OOP with hardly any of the downsides.
The PROBLEM with multidispatch match-on-type is that it causes it to be hard to determine when an object implements an interface or method and so it becomes easy to accidentally call types you didn't intended.
Also, thinking about state over objects is a step in the right direction, but the majority of programs already think in objects, and for the average programmer their natural mode of thinking is objects+action to mutate state. State is harder to reason about because it tends to be so mutable and dependant on run time.
A better solution is to disguise Julias type system in the language of OOP.
Datatypes becomes Classes.
Classes have methods through 'mixins' which are really just non-abstract base methods that use multidispatch, and the mixin method forms a contract that removes the risk of accidental type matching while also giving avenues for 1. self documenting code, 2. performance improvements.
ON AUTOMATIC RETURN
On systems designed to work or interoperate with C, automatic returns on the last statement could cause unexpected problems down the line in execution. Worse, for a beginner, these sorts of bugs are likely invisible, because the intent to return is assumed in languages where the last statement is ALWAYS treated as a return.
Worse still, this sort of design encourages 'pyramid coding recursion hell', where some users will first decompose their program into respective scopes, and then return and compose them..atomically as possible, meaning execution flow becomes distorted, run time state becomes dependant not on obvious plain-at-sight code, but on the run time state itself, and finally it encourages a form of thinking where programmers attempt to write code that 'just works' without thinking about how it *looks* or reads. The problem with opaque-programming is that while it may or may not be effortless, much more time is spent in reading, debugging, understanding, and maintaining code than is spent writing it--which is obviously problematic if we have a bunch of invisible returns everywhere, which requires new and even senior developers reading it down the line to stop each and every time to mentally 'insert' a return statement.
Wherever possible code should be WYSIWYG in terms of the effects per statement. Intent and the effects thereof should always be explicit per statement, not implicit, otherwise effects not intended will eventually slip in, and be missed.
It's hard to catch, and fix the effects of a statements intent where the statement is *implicit* because the effect is a *byproduct* of another statement.
ON DESTRUCTURING
Python does this as do a number of other languages, and it makes serialization/deserialization far easier.
In moonscript, the process of declaring a constructor means leads to arguments being initialized and bound automatically, so doing...
Class Foo
new(@x, @y, @z, @otherargs)
..which saves time. And unless you override those arguments, then x, y, z, and otherargs are implicitly declared and set to the value of the respective arguments that were passed in. Which is absolutely lovely.
PROPERTY ITERATORS
In kotlin theres some example code…
fun main() {
val pair = "Ferrari" to "Katrina" // 3
println(pair)
infix fun String.onto(other: String) = Pair(this, other) // 4
val myPair = "McLaren" onto "Lucas"
println(myPair)
val sophia = Person("Sophia")
val claudia = Person("Claudia")
val ron = Person("Ron")
sophia likes claudia // 5
claudia likes ron
ron likes sophia
ron likes claudia
println(sophia.likedPeople)
}
class Person(val name: String) {
val likedPeople = mutableListOf<Person>()
infix fun likes(other: Person) { likedPeople.add(other) } // 6
}
.. and I was thinking, if I wanted to list all the people Sophia likes, by name, then I'd have to do a foreach. But instead, what if I could do..
println(sophia.likedPeople.nameS)
or
println(sophia.likedPeople.name's)
or
println(sophia.likedPeople.name%s)
the idea being it looks like a string placeholder, but actually is an iterator placeholder for the given property in question.
RIPPED SHAMELESSLY FROM SWIFT
Nil-Coalescing Operator
The nil-coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression a is always of an optional type. The expression b must match the type that is stored inside a.
The nil-coalescing operator is shorthand for the code below:
a != nil ? a! : b
ONE SIDED RANGES
One-Sided Ranges
The closed range operator has an alternative form for ranges that continue as far as possible in one direction—for example, a range that includes all the elements of an array from index 2 to the end of the array. In these cases, you can omit the value from one side of the range operator. This kind of range is called a one-sided range because the operator has a value on only one side.
SCOPES AND OTHER RAMBLINGS
In go, you can create and initialize a variable in one line using ":="
which works well but can cause bugs if you miss the colon (which is easy to do)
A better approach is how javascript does it, requiring the
use of 'strict' (or leaving it out) to determine if things should be declared beforehand or not.
var is a better, clearer means of doing declarations on a line anyway. It's less likely to be skipped over or missed, it's simple, and it's familiar
Finally, lexical scoping makes WAY more sense than other types of scoping. It allows you to reason about scope simply by LOOKING at the code itself.
PACKAGES
For an understanding of what not to do, an illustrative example, look no further than the mess that is python package management, or the modular mess that is javascript. Dependency management is and always will be a nightmare. Golang gets this right by including all dependencies in the build itself where possible. Go had the right idea, unlike python. Compile EVERYTHING (where possible) into the executable so the program itself carries around it's own dependencies.
Do that. Developer experience is critical, and in a few years, you'll see people leaving android for apple development in droves because of googles failure to focus on developer experience.
TUPLES
If you allow tuples to bewritten like so..
(x=0; x<=10;x++)
or
(x=0, x<=10, x++)
Then for-loops become idiomatic, as well as method and function signatures.
GENERICS AND THINGS SHAMELESSLY STOLEN FROM A LANGUAGE CALLED KIT
Assuming you implement strict typing, you're probably going to want generics or type inference.
Now, in the case of generics, the problem with them is they almost always devolve into
a bunch of T's all scattered throughout your code, depending on the language, and make
it difficult to discern what the hell is going on.
I know C# used square brackets for generic types
But they ALSO used them for directives (because generics were compile time), which is actually pretty clever, giving you the syntax for generics and directives at the same time.
An extension of all this thinking is 'traits'.
Traits, which can be compared to typeclasses, are interfaces that can be implemented on some type.
Taken from a language called Kit:
"After a trait is implemented for a certain type, values of that type can be converted to "boxed" pointers
which look the same regardless of the type's identity; this enables open polymorphism.
CONCLUSION
These are just a set of ideas worth *exploring*. Not all of them are a good fit, and some of them are taste based, but if we're fielding suggestions for improvements, we have to start somewhere.