VSoft Technologies Blogs

rss

VSoft Technologies Blogs - posts about our products and software development.


Delphi Language Enhancements


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<T> : 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<Rate>) ;)

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<T> 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<boolean>('ForceUpdate', true) or x.IsType<IForceUpdate>).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<T:enum> = class
	    .....
	  end;
	

Benefits : more type safety, less runtime checking code required.

Fix IEnumerable<T>

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<T> Read<T>(string sql, Func<IDataReader, T> 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>: T;
	end;
	

That won't compile, but this code works as expected:

	TContainer = class
	  function Resolve<T>: 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 http://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.





Comments are closed.
Showing 5 Comments
Avatar  Joseph Mitzen 12 months ago

This is a great article, and a subject I've been championing as well for quite some time.

A few comments:

>where people don't want the c#/java/whatever features polluting their pure pascal code.

This drives me crazy. Other languages borrow good ideas all the time. Did these same people object to the adding of object orientation to Pascal?

Type inference, along with named arguments, are on my short list of about five or so features that would significantly improve the readability, user friendliness, or expressive power of the language. The language has the power to do named arguments under certain conditions with a long list of exceptions, but it isn't easy and is more of a hack:

http://stackoverflow.com/questions/885942/named-optional-parameters-in-delphi

I planned on writing a modern Delphi date/time unit inspired by ones I'd encountered in other languages, but gave it up when I realized that modern Delphi didn't really have named parameters as I initially thought it did. The ability to deal with dates and times like so would be fantastic compared to the non-object oriented functions currently in Delphi:

timestamp.replace(hour := 4, minute := 30); #Directly replace values

timestamp,replace(days := -3); #Shift value

Regarding variable method arguments, it is my understanding that this is already in Delphi:

http://stackoverflow.com/questions/6807066/pass-unlimited-number-of-parameters-to-procedure

The real challenge would be variable arguments of different types, which is rather clumsy now, and could be improved with a better type system. Ceylon's union types allow you to specify a list of potential types for a variable. This allows tricks like dictionaries with different types for the values, something I've only seen before in dynamically typed languages.

https://en.wikipedia.org/wiki/Ceylon_(programming_language)#Type_System

Regarding lambdas: this is what Delphi needed, not anonymous methods. A language with first class and nested functions doesn't need anonymous methods. Delphi's problem is the ancient single-pass compiler design, which requires nested functions to be defined at the beginning of the code block. If they'd dropped the ancient compiler limitation then functions could be defined where needed and anonymous methods would be superfluous and only lambdas would be of use. As noted, the current anonymous methods are bloated and make for hard-to-read code for that no style guide currently addresses (the official style guide still references Borland and CodeGear unfortunately).

LINQ: sigh. For eight years Allen Bauer and David Intersimone were talking about adding parallel programming features directly to the language. After myself and another person publicly shamed them for adding it to a roadmap yet again they finally rushed their Omnithread-wannabe library into production (which isn't the same thing) and stopped adding the parallel programming features to the roadmap. An interesting fact is that Allen Bauer's only official post on Reddit came when the general programming forum was criticizing Delphi's generics implementation. Allen appeared, saying that it should be understood that these generics were only the first step towards "LINQ-like functionality" and then language-level parallel programming features. Sadly, we never got that LINQ-like functionality either and I never saw it mentioned outside of that one post. LINQ and parallel programming features have been complete vaporware for Delphi.

We could obtain the same general benefits of LINQ by simply adding list comprehensions as found in languages such as Javascript and Python.

http://mark-dot-net.blogspot.co.uk/2014/03/python-list-comprehensions-and.html
http://mark-dot-net.blogspot.com/2014/03/python-equivalents-of-linq-methods.html

In terms of features that would be nice to have, I'd suggest the addition of tuples (including argument unpacking), generics for first class functions, removal of forward declarations and other single-pass compiler relics. Some things that have been missing since the days of Turbo Pascal, such as a step clause for the FOR loop and a power symbol (both of which are found in BASIC). Slice notation. Design by contract. Real sets! Contrary to claims and popular opinion, Delphi does not have sets. It has binary arrays. Real sets aren't limited to 255 values - in fact, ISO Pascal doesn't specify this and GNU Pascal allows more than 255 values. Sets are not over a contiguous range of values either. These two limitations make sets practically useless and I've never used them in Delphi outside of programming class assignments in school. My life deals with more than the numbers 0..255 or 'a'..'z'. Real sets, on the other hand, are much more useful. Imagine a set of STRINGS! This would be extremely useful, even for a simple case such as finding the unique words in a sentence. I've used real sets in other languages to create a set of hash values for the contents of two different file directories and then been able to create a set in one line of code of files found in one directory but not both.

Strings > 2GB and an actual stack trace would be some more items I'd add to the list of overdue improvements to the actual language (I can think of a lot more that could be accomplished but involve changes to the standard library).

One thing I'd suggest is that your improvement regarding Use clauses is needed but doesn't go far enough. Currently the Use clause pulls in the entire contents of a unit, leading to what other languages call "namespace pollution" as well as the risk of accidentally overwriting a previously imported function. This also leads to ambiguity in code. I once ran into some code on a blog page that was something like this...

Uses A, B, C, D, E, F, G, H, I, J, K, L, M, N, O;

...

SomeReallyCoolProcedure;

....

The units were a mix of standard libraries and commercial components. Which one had that really cool procedure I loved? I'll never know. :-( This is one of several ways the language is ambiguous.

The problem here is that it's completely backwards. Right now, the library WRITER controls everything, and that's insane. Control needs to be placed where it belongs, on the user's end. Do that, and everything changes.

First, importing a unit by name should require any imports to be properly referenced only. For instance,

Uses Whatever;

To use items from the unit, they should need to be referenced by full name only:

Whatever.Regression(x, y, z);

As an alternative, allow importing of individual items!

From Whatever import Regression;

Now, and only now, can you just reference "Regression" in your code. No namespace pollution, no chance of namespace collision, no ambiguity.

Delphi allows the library WRITER to rename an exported item, which is crazy. If a writer wanted to name something differently, they'd have named it that in the first place. Renaming only makes sense on the import end.

Import ReallyBigUnitName as RBUN;

This is handy for when two units have an item with the same name:

From Math Import Regression;
From NewMath Import Regression As New_Regression;

When you think about it, it's hard to understand what Anders was thinking in the first place. The time to start eliminating the ambiguities in the language is long overdue.

Anyway, fantastic list. Posts like these surface from time to time, but despite what Marco Cantu may say, no one involved in the language pays any attention to them and I can't think of a feature that ever originated from the community. MVP Craig Stuntz proposed adding list comprehensions to Delphi, including designing a syntax, eight years ago now and submitted it to the proper quality whatever-they-call-it. No one from Embarcadero even added a remark to their proposal. He also had some of the same ideas you did:

https://groups.google.com/d/msg/borland.public.delphi.non-technical/8NZExLhm6BA/IHG_B6yaln8J

Avatar  Joseph Mitzen 12 months ago

@Rudy

I agree regarding the terse C syntax for the ternary operator. Python does it somewhat like your example, except switched around:

x = 0 if y == 0 else 99

Avatar  Rudy Velthuis 12 months ago

To @Uwe: I actually like the ternary operator, but not with the ugly C syntax. I'd prefer the Algol-like

x := if y = 0 then 0 else 99;

That does the same but has a much more Delphi-like syntax. Note that this is not the same as some of the IfElse functions in the runtime. These are functions, and evaluate both branches before they are called. A real ternary would only evaluate one of the branches.

Avatar  Uwe 12 months ago

Well, most of your ideas good and i wish they where inside Delphi :).
Some of your ideas i knew from other languages (JS or php), and i hope, i never have to read such things in delphi (like x := y = 0 ? 0 : 99;)

This things make code less readable and understandable.

But..as you said...all optional so use it or not, it's the choice of the developer :)


BTW:

For Local initalized variables....just use :

const
I : integer = 0;

:)

Avatar  Mason Wheeler 12 months ago

WRT Microsoft holding patents on various concepts, it's worth noting that two years ago, <a href="https://en.wikipedia.org/wiki/Alice_Corp._v._CLS_Bank_International">the Supreme Court held that software implementations of abstract ideas are not patentable,</a> a decision which <a href="http://www.iam-media.com/blog/Detail.aspx?g=2028b324-2d4a-4523-9f0d-f0773b8b3fa1">even those strongly in favor of software patents</a> believe invalidates huge swaths of major software companies' patent portfolios.

IANAL, but it seems obvious that people implementing their own versions of LINQ and async/await have nothing to fear here.