TL;DR - The Delphi language is very verbose, dated and unattractive to younger developers. Suggestions for improvements below.
The Delphi/Object Pascal language really hasn't changed all that much in the last 20 years. Yes there have been some changes, but they were mostly just tinkering around the edges. Probably the biggest change was the addition of Generics and Anonymous methods. Those two language features alone enabled a raft of libraries that were simply not possible before, for example auto mocking (Delphi Mocks, DSharp), dependency injection and advanced collections (Spring4D).
Some of the features I list below have the potential to spur on the development of other new libraries which can only be a good thing. I have several abandoned projects on my hard drive that were only abandoned because what I wanted to do required language features that just didn't exist in Delphi, or in some cases, the generics implementation fell short of what was needed. Many of these potential features would help reducing verbosity, which helps with maintainability. These days I prefer to write less lines of more expressive code.
I have tried to focus on language enhancements that would have zero impact on existing code, i.e. they are completely optional. I have often seen comments about language features where people don't want the c#/java/whatever features polluting their pure pascal code. My answer to that is, if you don't like it don't use it!! There are many features in Delphi that I don't like, I just don't use them, but that doesn't mean they should be removed. Each to his own and all that. Another argument put forward is "feature x is just syntactic sugar, we don't need it". That's true, in fact we don't need any language if we are ok with writing binary code! If a feature is sugar, and it helps me write less code, and it's easier to read/comprehend/maintain, then I love sugar, load me up with sugar.
Inspiration
Lots of the examples below borrow syntax from other languages, rather than try to invent some contrived "pascalish" syntax. The reality is that most developers these days don't just work with one programming language, they switch between multiple (I use Delphi, C#, Javascript & Typescript on a daily basis, with others thrown in on occasion as needed). Trying to invent a syntax just to be different is pointless, just borrow ideas as needed from other languages (just like they did from delphi in the past!) and get on with it!
I have not listed any functional programing features here, I have yet to spend any real time with functional programming, so won't even attempt to offer suggestions.
I don't have any suggestions for how any of these features would be implemented in the compiler, having not written a real compiler before I know my limitations!
Ok, so lets get to it, these are not listed in any particular order.
Local Variable Initialisation
Allow initialisation of local variables when declaring them, eg :
procedure Something;
var
x : integer = 99;
begin
.........
Benefits : 1 less line of code to write/maintain, per variable, initial value is shown next to the declaration, easier to read.
Type Inference
var
x = 99; //it's an integer
s = 'hello world'; //it's a string
begin
........
Benefits : less ceremony when declaring variables, still easy to understand.
Inline variable declaration, with type inference and block scope
procedure Blah;
begin
.......
var x : TStrings := TStringList.Create; //no type inference
//or
var x := TStringList.Create; // it's a TStringList, no need to declare the type
.......
end;
Inline declared variables should have block scope :
if test = 0 then
begin
var x := TListString.Create;
....
end;
x.Add('bzzzzz'); //Compiler error, x not known here!
Benefits : Declare variables when they are needed, makes it easier to read/maintain as it results in less eye movement, block scope reduces unintended variable reuse.
Loop variable inline declaration
Declare your loop or iteration variable inline ( and they would have loop block scope)
for var item in collection do
begin
item.Foo;
end;
item.Bar; //error item unknown.
for var i : integer := 0 to count do
....
//or
for var i := 0 to count do //using type inference
....
Benefits : Avoid the old variable loop value not available outside the loop error, same benefits as inline/block scope etc.
Shortcut property declaration
Creating properties that don't have getter/setter methods in Delphi is unnecessarily verbose in Delph
type
TMyClass = class
private
FName : string;
public
property Name : string read FName write FName;
end;
All that is really needed is :
type
TMyClass = class
public
property Name : string;
end;
Whilst this might seem the same as declaring a public variable, RTTI would be generated differently if it was a variable rather than a property.
Benefits : Cuts down on boilerplate code.
Interface Helpers
Add interface helpers just like for classes and records. Also, remove the limit of one helper per type per unit (without this the above is not really usable). Have a look at how prevalent extension methods are in C#, Linq is a prime example, it's essentially just a bunch of extension methods.
type
TIDatabaseConnectionHelper = interface helper for IDatabaseConnection
function Query : IQuerable;
end;
The above is actually a class that extends the interface when it's containing unit is used. This would make implementing something like LINQ possible.
Strings (and other non ordinals) in Case Statements
This has to be the most requested feature by far in the history of delphi imho, sure to make many long time Delphi fans happy.
case s of
'hello' : x := 1;
'goodbye' : x := 2;
sWorld : x := 3; //sWorld is a string constant
end;
Case sensitivity could be possibly be dealt with at compile time via CaseSensitive 'helper', (or perhaps an attribute). The above example is case insensitive, the example below is case sensitive :
case s.CaseSensitive of
'hello' : x := 1;
'goodbye' : x := 2;
sWorld : x := 3; //sWorld is a string constant
end;
//or
case [CaseSensitive]s of
'hello' : x := 1;
'goodbye' : x := 2;
sWorld : x := 3; //sWorld is a string constant
end;
How about :
case x.ClassType of
TBar : x := 1;
TFoo : x := 2;
end;
Benefits : Simpler, less verbose code.
Ternary Operator
A simpler way of writing :
If y = 0 then
x := 0
else
x := 99;
Syntax : x := bool expr ? true value, false value
//eg
x := y = 0 ? 0 : 99;
Benefits : Simpler, more succinct code.
Try/Except/Finally
Probably the equal most requested language feature, allowing for try/except/finally without nesting try blocks.
try
...
except
..
finally
...
end;
Much cleaner than:
try
try
...
except
...
end;
finally
....
end;
Benefits : Neater, Tidier, Nicer
Named Arguments
Say for example, we have this procedure, with more than one optional parameter :
procedure TMyClass.DoDomething(const param1 : string; const param2 : ISomething = nil; const param3 : integer = 0);
To call this method, if I want to pass a value for param3, I have to also pass a value for parm2
x.DoSomething('p1', nil,99);
With named parameters, this could be :
x.DoSomething('p1', param3 = 99);
Yes, this means I have to type more, but it's much more readable down the track when maintaining the code. I also don't need to lookup the order of the parameters, or provide redundant values for parameters that I want to just use their default values for. I also don't end up adding a bunch of overloaded methods to make the method easier to call.
Benefits : More expressive method calls, less overloads.
Variable method arguments
Steal this from C# (which probably borrowed the idea from c++ varargs ... feature)
procedure DoSomething(params x : array of integer);
The method can be called passing ether an array param
procedure TestDoSomething;
var
p : array of integer;
begin
...... //(fill out the p array)
DoSomething(p);
//or
DoSomething(1);
DoSomething(1,2);
DoSomething(1,2,3);
........
Benefits : Flexibilty in how a method is called.
Lambdas
Delphi's anonymous method syntax is just too damned verbose, wouldn't you prefer to write/read this:
var rate := rates.SingleOrDefault(x => x.Currency = currency.Code);
rather than this :
var
rate : IRate;
begin
rate := rates.SingleOrDefault(function(x : IRate) : boolean;
begin
result := x.Currency = currency.Code;
end);
...
Ok, so I did sneak in an inline type inferenced local var in the first example, and both snippets rely on the existence of interface helpers (rates would be IEnumerable ) ;)
Benefits : Less code to write/maintain, the smiles on the faces of developers who switch between delphi and c# all the time!
LINQ!
With Lambdas and multiple type helpers per type per unit, a LINQ like feature would possible. Whilst the Delphi Spring library has had Linq like extensions to IEnumerable for a while, this would formalise the interfaces and provides an implementation used by the collection classes. Providers for XML and Database (eg FireDac) would be possible.
Even a Linq 2 VCL & Linq 2 FMX would be possible. A common scenario, update a bunch of controls based on some state change :
Self.Controls.Where( x => x.HasPropertyWithValue('ForceUpdate', true) or x.IsType).Do( c => c.Refresh); //or something like that.
This would make it possible to operate on controls on a form, without a) knowing their names, b) having references to them, or c) knowing if the control actually exists.
Being able to do this with something like a linq expression would be a massive improvement.. all that's needed is a linq provider for each control framework. Sorta Like jQuery for the VCL/FMX!
Benefits : Too many to list here!
Caveats : Microsoft have a patent on LINQ - so perhaps this isn't really doable? Perhaps with a slightly different syntax. Or challenge the patent!
Async/Await
Bake the parallel library features into the language/compilers, much like C# and other languages have done over recent years (ala async, await). That makes it possible/easier to write highly scalable servers, over the top of asynchronous I/O. Yes, it's technically possible now, but it's so damned difficult to get right/prove it's right that very few have attempted it. This also make it possible to use the Promise pattern, something along these lines - https://github.com/Real-Serious-Games/C-Sharp-Promise to handle async tasks.
Benfits : Write scalable task oriented server code without messing with low level threading, write responsive non blocking client code.
Caveats : Microsoft have a patent on Async/Await
Non reference counted interfaces
Make it possible to use have interfaces without reference counting. I use interfaces a lot, even on forms and frames, I like to limit what surface is exposed when passing these objects around, but it can be painful when the compiler tries to call _Release on a form that is being destroyed. Obviously, there are ways to deal with this (careful clean up) but it's an easy trap to fall in and very difficult to debug.
Possible syntax :
[NoRefCount] // << decorate interface with attribute to tell compiler reference counting not required.
IMyInterface = interface
....
end;
There would have to be some limits imposed (using the next feature in this list), for example not allowing use of the attribute on an interface that descends from another interface which is reference counted. It would cause mayhem if passed to a method that takes the base interface (for which the compiler would then try generate addref/release calls).
Benefits : Remove the overhead of reference counting, avoid unwanted side effects from compiler generated _Release calls.
Attribute Constraints
(RSP-13322)
Delphi attributes do not currently have any constraint feature that allows the developer to limit their use, so for example an attribute designed to be applied to a class can currently be applied to a method.
Operator overloading on classes.
Currently operator overloading is only available on records, adding them to classes would make it possible to create some interesting libraries (DSL's even).
I had discussions with Allen Bauer (and others over) many years ago about about this. Memory management was always the stumbling block, with ARC this would not be big issues. Even without ARC, I think delphi people are capable of dealing with memory management requirements, just like we always have.
Edit : I believe this feature is actually available on the ARC enabled compilers.
Improve Generic Constraints
Add additional constraint types to generics, for example :
type
TToken = class
.....
end;
Benefits : more type safety, less runtime checking code required.
Fix IEnumerable
Not so much a language change rather than a library change, please borrow spring4d's version - much easier to implement in 1 class (delphi's version is impossible to implement in 1 class as it confuses the compiler!).
Yield return - Iterator blocks
The yield keyword in C# basically generates Enumerator implementations at compile time, which allows the developer to avoid writing a bunch of enumerator classes, which are essentially simple state machines. These enumerators should be relatively easy for the compiler to generate.
This a a good example (sourced from this stackoverflow post
public IEnumerable Read(string sql, Func make, params object[] parms)
{
using (var connection = CreateConnection())
{
using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
{
command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return make(reader);
}
}
}
}
}
The example above will read the records in from the database as they are consumed, rather than reading them all into a collection first, and then returning the collection. This is far more memory efficient when there are a lot of rows returns. In essence, the consumer/caller is "pulling" the records from the database when required, this could for example be pulling messages from a queue.
I guess for delphi, this could be a simple Yield() method( or YieldExit() to more more explicit).
Benefits : less boilerplate enumerator code, lower memory usage when working with large or unbounded datasets or queues etc.
Partial classes
Yes I know, this one is sure to kick up a storm of complaints (I recall an epic thread about this topic on the newsgroups about this years ago). Like everything else, if you don't like it, don't use it. I don't often use this feature in C#, but it is indispensable for one particular scenario, working with generated code. Imagine generating code from an external model or tool for example type libraries, uml tools, database schema, orm, idl etc. The generated code would typically be full of warning comments about how it's generated code and shouldn't be modified. Partial classes get around this, by allowing you to Add code to the generated classes, and the next time they are regenerated, your added code remains intact. Simple, effective.
Benefits : Enables code generation scenarios without requiring base classes.
Allow Multiple Uses clauses
(RSP-13777)
This is something that would make commenting/uncommenting units from the uses clause a lot easier, and also make tooling of the uses clause easier.
uses
sharemem, System.SysUtils, System.Classes, vcl.graphics{$ifdef debug},logger{$endif};
The above syntax is easily messed up by the IDE.
uses sharemem;
uses System.SysUtils, System.Classes;
uses vcl.graphics;
{$IFDEF debug}uses logger;{$endif}
This makes commenting-out and reorganising the library names more convenient. It would also make refactoring and other tooling easier.
Allow non parameterized interfaces to have parameterized methods
(RSP-13725)
IContainer = interface
['{2B7B3956-7101-4619-A6DA-C8AF61EE4A81}']
function Resolve: T;
end;
That won't compile, but this code works as expected:
TContainer = class
function Resolve: T;
end;
This is a limitation I have come across many times when trying to port code from C# to Delphi.
Conclusion
That's 20+ useful, optional and simple to implement (just kidding) language features that I would personally like to see in Delphi (and I would use every one of them). I could have gone on adding a lot more features, but I think these are enough to make my point.
Here's my suggestion to Embarcadero, invest in the language and make up for lost time! While you are at it, sponsor some developers to try porting some interesting and complex open source libraries to delphi, and when they hit road blocks in the language or compiler(and they will), make it work, iterate until baked. I tried porting ReactiveX https://reactivex.io/ to delphi a while back, but hit too many roadblocks in the language and compiler (internal compiler errors, limitations in the generics implementation).
The end result would be a modern, capable language that can handle anything thrown at it.