I hope you meant to say "*forced* indention for code blocks"! "Forced"
being the key word here. What about tabs over spaces, have you decided
the worth of one over the other or are you going to repeat Guido's
folly?
And please, i love Python, but the language is a bit asymmetrical. Do
try to bring some symmetry to this new language. You can learn a lot
from GvR's triumphs, however, you can learn even more from his follys.
Personally, I find a lot of good things in Python. I thinking tabs are
out-of-date. Even the MAKE community wishes that the need for tabs would
go away and many implementations have done just that. I have been
seriously debating about whether to force a specific number of spaces,
such as the classic 4, but I am not sure yet. Some times, 2 or even 8
spaces is appropriate (although I'm not sure when).
I have always found the standard library for Python to be disjoint. That
can be really beneficial where it keeps the learning curve down and the
size of the standard modules down. At the same time, it means
re-learning whenever you use a new module.
My language combines generators and collection initializers, instead of
creating a whole new syntax for comprehensions.
[| for i in 0..10: for j in 0.10: yield return i * j |]
Lambdas and functions are the same thing in my language, so no need for
a special keyword. I also distinguish between initialization and
assignment via the let keyword. Also, non-locals do not need to be
defined explicitly, since the scoping rules in Unit are far more "anal".
In reality though, it takes a certain level of arrogance to assume that
any language will turn out without bumps. It is like I was told in
college long ago, "Only the smallest programs are bug free." I think the
same thing could be said for a language. The only language without flaws
would be so small that it would be useless.
I love these types of discussions though, because it helps me to be
aware. When designing a language, it is extremely helpful to hear what
language features have led to problems. For instance, C#'s foreach loops
internally reuse a variable, which translates to something like this:
using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
{
T current;
while (enumerator.MoveNext())
{
current = enumerator.Current;
// inner loop code goes here
}
}
Since the same variable is reused, threads referencing the loop variable
work against whatever value is currently in the variable, rather than
the value when the thread was created. Most of the time, this means
every thread works against the same value, which isn't the expected
outcome. Moving the variable inside the loop _may_ help, but it would
probably be optimized back out of the loop by the compiler. With the
growth of threaded applications, these types of stack-based
optimizations may come to an end. That is why it is important for a
next-gen language to have a smarter stack - one that is context
sensitive. In Unit, the stack grows and shrinks like a dynamic array, at
each scope, rather than at the beginning and end of each function. Sure,
there's a slight cost in performance, but a boost in consistency. If a
programmer really wants the performance, they can move the variable out
of the loop themselves.
In fact, there are a lot of features in Unit that will come with
overhead, such as default arguments, non-locals, function-objects, etc.
However, the game plan is to avoid the overhead if it isn't used. Some
things, such as exception handling, will be hard to provide without
overhead. My belief is that, provided a tool, most developers will use
it and accept the slight runtime overhead.
I think everyone has an idea about what would make for the perfect
language. I am always willing to entertain ideas. I have pulled from
many sources: C#, Java, Python, JavaScript, F#, Lisp and more. The hope
is to provide as much expression with as much consistency as possible.
Just the other day I spent 2 hours trying to determine how to create a
null pointer (yeah, it took that long).
let pi = null as shared * Integer32 # null is always a pointer
Originally, I wanted 'as' to be a safe conversion. However, I decided to
make use of the 'try' keyword to mean a safe conversion.
let nd = try base as shared * Derived let d = if nd.Succeeded: nd.Value
else: null # or, shorthand let i = try Integer32.Parse("123") else 0
Of course, the last line could cost performance wise. For that reason,
Unit will allow for "try" versions of methods.
let Parse = public static method (value: String) throws(FormatException
UnderflowException OverflowException) returns(Integer32): ...
and Parse = public static try method (value: String)
returns(TryResult<Integer32>): ...
Of course, such methods are required to never throw and must return a
specific named tuple type.
Type, type, type. In short, I can only hope my language is useful and
that I dodge as many inconsistencies as possible. The good thing about
an unknown language is that no one gets mad when things change. In that
sense, Python's annoyances are probably an indication of its quality.