Coworker and me were just thinking if this (or similar syntax) would be nice to have in Delphi:

Coworker and me were just thinking if this (or similar syntax) would be nice to have in Delphi:

car := TCar.Create
begin
Name := 'Chevrolet Corvette';
Color := TColors.Yellow;
end;

Edit: After pointing out this might lead to code defects because begin end can be used regardless this feature here is another proposal (similar to how Oxygene does it):

car := TCar.Create(
Name: 'Chevrolet Corvette',
Color: Colors.Yellow,
MaxSpeedKm: 300
);

With a ctor that has an argument:

car := TCar.Create('Chevrolet Corvette'; // <- semicolon
Color: Colors.Yellow;
MaxSpeedKm: 300
);


Suggestion by Eric Grange - like the original one but reusing the with to make it safe against issues using begin:

car := TCar.Create
with
Name := 'Chevrolet Corvette';
Color := Colors.Yellow;
MaxSpeedKm := 300
end;

Ctor with parameter:

car := TCar.Create('Chevrolet Corvette')
with
Color := Colors.Yellow;
MaxSpeedKm := 300;
end;

Similar to how since C# 3.0 you can initialize objects (see http://csharp.net-tutorials.com/csharp-3.0/object-initializers/)

or Oxygene: https://docs.elementscompiler.com/Oxygene/Members/Constructors/ -> "Extended Constructor Calls"

This is only allowed on a constructor call - that is not a with equivalent

Edit:

Here are the rules:
1) only assignments (or similar syntax with a colon like in c# or an equals sign like for default values in delphi or global variable initialization) allowed
2) only publicly accessible properties of the created objects are allowed on the left side of the expression
3) right side of the expression can be any expression compatible to the left side but not a member of the created instance

Comments

  1. Juan C. Cilleruelo Why? I would think of this as a general extension to a constructor call. Since the instance can be passed somewhere else you dont even need a local variable.

    ReplyDelete
  2. Stefan Glienke
    because inheritance, in this mode, can be less clear.

    ReplyDelete
  3. Juan C. Cilleruelo What can be unclear here? Any writeable property a TCar variable would expose can be used. If you write TSuperCar.Create then any public property a TSuperCar has can be used.

    ReplyDelete
  4. Stefan Glienke
    I hate the use of "with". May be I'm anchored in this idea.

    Really sounds good.

    ReplyDelete
  5. This kind of thing would be useful for replacing

    with TLabel.Create(self) do...

    or similar constructs where rules are not as strict and you can either shoot yourself in the foot or you have to declare extra variable you don't actually need later on.

    ReplyDelete
  6. I don't quite see the benefit of this over a constructor and passing the values in as parameters. Could you explain that, please?

    Eg, what is better about,

    car := TCar.Create
    begin
    Name := 'Chevrolet Corvette';
    Color := TColors.Yellow;
    end;

    than

    car := TCar.Create(Chevrolet Corvette', TColors.Yellow);

    ?

    You mention it's only for publicly accessible properties, is that a hint about the use case / benefit?

    ReplyDelete
  7. David Millington Think of classes with a lot of properties (like VCL/FMX) ones where it would be impossible to have all constructor variations you could need.

    This would be something like instant Builder pattern.

    ReplyDelete
  8. David Millington You know that I would need n! (factorial) ctor overloads for n properties, yes?

    Have you read Dalija Prasnikar comment showing one of the possible use cases?

    You want to create a million ctor overloads for all the existing classes out there?

    Read the papers about object initializers, they are full of additional use cases.

    ReplyDelete
  9. IMO, Stefan Glienke - this is very close to a with and all it's pitfalls.

    Examples with errors (and I KNOW I will type that semi-colon by habit):
    var
    Name: String;
    begin
    // Name gets set somehow
    car := TCar.Create;
    begin
    Name := Name;
    Color := TColors.Yellow;
    end;

    Two errors: A semi colon behind the create;
    No scope in the block that will identify Name as a misreference.

    I'd prefer a syntax that has similarities to constant initializations:

    var
    Name: String;
    begin
    // Name gets set somehow
    car := TCar.Create[
    Name:Name;
    Color:TColors.Yellow
    ];

    Square brackets to not confuse with constructor arguments, and be combinable with constructor arguments.

    left side = always internal scope
    right side = implicit external scope

    The right side allows full expressions - with self.<prop.name> for internal scope references.

    ReplyDelete
  10. Named parameters are already supported for com objects. It would be nice if it would work on "plain" Delphi functions and methods too.

    Some extra help to construct objects would be welcome, especially it anonymous types and more advanced type inference would be added.

    But how many years or decades will it take for the code editor to catch up with the compiler? I see a future of red wiggly lines in the IDE.

    ReplyDelete
  11. Lars Fosdal Good points about the potential code defects.
    I dislike the usage of square brackets because they usually have another meaning.

    That is one of the reasons I am posting this here and not opening a ticket (yet) fyi Horácio Filho

    ReplyDelete
  12. Stefan Glienke I'd use regular parantheses if they weren't already used for ctor arguments.

    What about <> ? Constructors are not allowed to have generic type specifiers, AFAIK.

    var
    Name: String;
    begin
    // Name gets set somehow
    car := TCar.Create<
    Name:Name;
    Color:TColors.Yellow
    >;

    ReplyDelete
  13. Lars Fosdal Would conflict with generic parameters.

    Maybe the oxygene approach could work by adding those after the ctor arguments.

    Let's say we have a ctor with the name parameter.

    car := TCar.Create('chevy', Color: clYellow);

    using the parameterless ctor would look like this:

    car := TCar.Create(Name: 'chevy', Color: clYellow);

    ReplyDelete
  14. Lars Fosdal I have a great dislike for any new syntax which places such great emphasis on a character which has historically been a separator. Or, for that matter, on any single character, which is one reason I have always preferred begin/end to C's {}. It is also why I have disliked the C ternary operator.

    I agree that the reflex habit of adding the semicolon would be an annoyance in writing this construct. On the other hand, I am not thrilled at the use of square brackets, either. The overloaded meanings of such symbols becomes a challenge to readability, in my view.

    ReplyDelete
  15. Stefan Glienke syntactically, it would work - since they would be used after the actual generic references.

    constructor Create(aParam:TGenType)

    ReplyDelete
  16. Bill Meyer It's not easy to reduce code clutter and satisfies aesthetics and at the same time have something that doesn't break easily.

    Stefan Glienke - I think named arguments might actually be the better option, but can/should we combine them with explicit arguments?.

    ReplyDelete
  17. I'd love a syntax like this. But I also know from the past that the Delphi team is very hesitant introducing new language features especially with ones that introduce new keywords or operators. That was before the Delphi team got decimated.

    ReplyDelete
  18. Stefan Glienke I much prefer the revised syntax to the original. The use of named parameters makes clear that this is not an explicitly overloaded ctor, and eliminates my earlier objections.

    ReplyDelete
  19. What is the difference between your syntax and

    car := TCar.Create;
    car.Name := 'Chevrolet Corvette';
    car.Color := Colors.Yellow;

    or in other words, is it a "with" clause in disguise?

    For instance would it allow the following code?

    Name := 'FooBar';
    car := TCar.Create( Engine := TEngine.Create( Name := Name ), Name := Name );

    Ot if it does not scope the read & write accessors differently, the following would be accepted?

    car := TCar.Create( Tire1 := 'Michelin MX'; Tire2 := Tire1; Tire3 := Tire1; Tire4 := Tire1);

    ReplyDelete
  20. Eric Grange I am confused. How do you leap from the named parameter notation into the use of embedded assignments?

    ReplyDelete
  21. About named parameters:
    You can already write foo.bar(x := 'yyy'); in Delphi. This syntax seems to be supported since the early days, but only on automation objects.

    Why introduce a slightly new syntax to do the same? The current one looks quite OK to me. It just needs to be supported on plain delphi functions.

    ReplyDelete
  22. Bill Meyer initial post mentions "only publicly accessible properties" in point 2, I understood it as meaning more than named parameters. Named parameters would be something else.

    Point 3 means my second example with FooBar is valid, but the last one is not.

    If all the above is correct, then it's more like a corner-case "with", that likely introduces more ambiguity than it solves.

    In the grand scheme it has the potential of being just as messy as a "with", as after all, "with" does not just fails on read/write access ambiguity.



    ReplyDelete
  23. Eric Grange It does not require some extra variable, enables direct passing of the ctor result somewhere else.

    The syntax of course should be safe (see my rules at the bottom of the original post - might need to F5 in your browser, g+ is not very good at refreshing edited posts with the new design). I think it has more in common with named parameters than with "with".

    Nesting those statements - could be possible but needs to solve the scoping properly, I would have to check if that is allowed in the other languages or not. If there is no easy to implement or understandable way, well then don't allow it.

    Checked what c# does. It only allows left side be properties of the object you are creating. Right side cannot be any of those properties. That should solve any ambiguity.

    this is valid c# code:

    public class Engine
    {
    public string Name { get; set; }
    }

    public class Car
    {
    public Engine Engine { get; set; }
    public string Name { get; set; }
    }

    var Name = "chevy";
    var car = new Car()
    {
    Name = Name, // car.Name = Name variable
    Engine = new Engine()
    {
    Name = Name // car.Engine.Name = Name variable
    }
    };

    ReplyDelete
  24. Stefan Glienke The fundamental difference with named parameters would be that named parameters are just syntax sugar for reordering and skipping parameters, but the values of the parameters are still evaluated by the caller and pushed to the stack.

    On the other hand, property assignments can have side effects, so the assignment order is important (not just order of evaluation), and code outside the caller will be involved in between the assignments (all the setters), so this is really a different code flow.

    for instance with

    TFoo.Create(Alpha : Expression1; Beta : Expression2)

    under named parameters you would have

    - evaluate Expression1, push to stack
    - evaluate Expression2, push to stack
    - call TFoo.Create

    with your proposal you would have either

    - call TFoo.Create, assign to _hidden_var
    - evaluate Expression1, push to stack
    - call _hidden_var.SetAlpha
    - evaluate Expression2, push to stack
    - call _hidden_var.SetBeta

    or if you favor early evaluation

    - evaluate Expression1, store in _temp1
    - evaluate Expression2, store in _temp2
    - call TFoo.Create, assign to _hidden_var
    - call _hidden_var.SetAlpha (_temp1)
    - call _hidden_var.SetBeta(_temp2)

    and there are more "valid" implementation scenarios, each with a different set of side-effects.

    Also is not requiring a variable really desirable? It makes it impossible (or at least very complicated) to inspect the instance that was created when debugging.
    And as noted above, you will really have calls to setters, so stepping and breakpoint are going to be desirable at some point.

    ReplyDelete
  25. We basically desire a single source "with" that enforced left side references to belong to the instance.

    ReplyDelete
  26. What I want is inline var/type deduction in a for in loop

    ReplyDelete
  27. To add my own low value observation on the issue of defects in design, I will offer this short list of large issues with the with clause:
    - stupidly allows: with a, b, c do
    - not supported by debugger

    The application of with to multiple objects could have been closed off years ago. With breakage of existing code, yes, but code which relied on that behavior should have been fixed, anyway.

    Can anyone defend the debugger not supporting the with clause? It was supposedly a basic feature of the language, and should have had full support. If that was impractical to achieve, then I submit the feature should have been removed.

    But of course, we have a long trail of issues and concerns with debugging which have never been resolved, so we code to avoid those issues.

    And no, I am NOT lobbying to rehabilitate the with clause.

    ReplyDelete
  28. FWIW (I just learned that) - a benefit of object initializers in C#: https://stackoverflow.com/a/12842511/587106

    ReplyDelete
  29. Stefan Glienke if you don not want to get down and dirty with technicalities, as you are certainly aware, the problem with language constructs is not how you will use it, but how it will be used by others.

    And given you are one of the few regularly complaining about things not done according to the book in the Delphi code... not without reason, but hey, that's my point ;-)

    Yes, the Delphi debugger is bad, and yes, the gap has been growing worse, and yes, it does not know about capture, and too many "new" language features are not supported...

    > do you know that the C# debugger can show results of previously executed functions?

    Not used C# in years, but I entirely expect the C# debugger to have that (and a lot more).

    The debugger I am using the most these days is Chrome's, and it runs circles around Delphi's in terms of... everything really.

    > So how does shortages of one thing affect the improvement of the language

    By turning more and more areas of Delphi language into debugging black holes, and making it more and more painful to use, because hey, code has bugs.

    Code that cannot be debugged means code that has to be rewritten, sonner or later, so potential gains quickly turn into wastes.

    ReplyDelete
  30. Eric Grange Agreed that priorities should not be:

    1. new features
    2. fix tooling so old new features are working
    3. make tooling aware of new features

    but first close the gap. Anyway at least for code tools (debugger might be a different story) going the way Microsoft did is the right one (make one compiler, not several different ones used for different areas). Otherwise tooling will always stay behind or not even possible or so complicated in terms of having to reinvent the wheel. God know how many different Delphi parsers are out there for all kinds of plugins that do search, highlighting, refactoring, documenting or whatever.

    That's the fantastic thing they did with TypeScript and Roslyn. They implement a new language feature and boom tooling has access to it for free.

    ReplyDelete
  31. Stefan Glienke yes, after working some time with typescript, delphi ide and tools feels old sometimes

    ReplyDelete
  32. Stefan Glienke if C# is never partially initialized in that case, then this adds an extra requirement, which is that setters have no provable side-effects and no external references, ie. either straight fields or something the compiler can ensure.

    If that requirement is added, then yes, it would be closer to named parameters and anonymous class constructors (as in JS or DWScript).

    Given that restriction, and if you do not want to forbid constructor parameters, a workable syntax could be

    instance := TCar.Create(regular params) with
    Field1 := "whatever";
    Prop2 := foobar;
    end;

    might as well recycles the "with" keyword hehe... the "with" would replace the '{' of C# (and the end for '}'), it "reads" fine, is short, and would put a nail in the coffin of the old usage of 'with'.

    I also agree with your priorities post... First thing to do when installing Delphi is still to deactivate features like error insight and such, because when they're not incorrect, they'll crash the IDE at the first opportunity. Pity because error insight as a feature is really useful.


    ReplyDelete
  33. Eric Grange Nice, i like that Syntax!

    ReplyDelete
  34. Eric Grange These could be the first useful use case of with.....

    ReplyDelete
  35. Error Insight is full of errors, but if you always fully qualify your unit names, at least it is not slow. That's a start.

    ReplyDelete
  36. Wouter van Nifterick
    "Named parameters are already supported for com objects. It would be nice if it would work on "plain" Delphi functions and methods too."

    Where are all of you people in the other thread where I raised the idea of named/optional parameters and people are personally attacking me? :-(

    plus.google.com - Ondrej Kelle - Google+

    I give some good and simple examples of their usefulness in the comments. Honestly, once you use them, it's hard to go back to not having them. They make APIs beautiful.

    ReplyDelete
  37. "what's the difference between ..."

    So why bother with any of this? You can do whatever you want in assembly language!

    For simple classes, named parameters look like overkill. Or 'with' statements. Or whatever your favorite poison might be. So what? Have you never had to deal with ctors that have 40 parameters? Yes, that could be said to be "bad form" but some people find it equally objectionable to create a record or class simply to pass a bunch of parameters into an object.

    Constructor injection has the benefit of allowing the compiler to tell you you're missing arguments, while property injection (either after the ctor call or as values buried inside of a record or class passed as a single parameter) gives no such benefit. But property injection lets you delay initialization of properties beyond object creation but prior to use.

    Why should we be forced to pick one over the other based on syntactical limitations imposed by language bigots? But, hey, that's why we're still using the same inefficient programming processes that were introduced back in the 1950s!

    In fact, there are at least eight (8) different mechanisms I've seen people employ to initialize properties within a class either directly from a ctor or immediately before or after calling it.

    It would be really nice for a change to see language enhancements like this that provide a single direct and clearly-defined path to do something that is fairly ubiquitous, that most of us have solved numerous times in numerous ways, and that most other languages have incorporated into their syntax.

    I think it's really sad to keep seeing discussions like this that clearly point out issues that beg for a single consistent and easy-to-use approach that's defined within the language, yet we can't have it because so many people can't resist bellyaching about theoretical crap regarding the syntax or because the proposed solution doesn't fit into their notion of "language purity".

    And the people who ultimately make the decisions have a history of doing nothing.

    Imagine if Wirth had never included a CASE statement with the language. We'd be arguing about how unnecessary it is because you just need a bunch of cascaded IF...THEN...ELSE statements. Really. What possible justification can there be for a CASE statement when most of the arguments being presented here about named arguments would apply equally to it? Thankfully it was part of the original language spec, which has spared us from two decades of needless debates about its utility!

    Consider what happens when people ask for CASE statements that support strings like so many other contemporary languages. KA-BOOM! They're blown out of the water for being language heretics.

    If these things are such bad ideas, why are they being included in the vast majority of languages developed over the past decade?

    I believe that programming languages should SUPPORT COMMON PROGRAMMING NEEDS! Which is what we see showing up in newer languages.

    How can anybody hope to get new users on-board with Delphi if it looks like a throwback to the 70's in terms of language features?

    There are so many areas that newer languages allow programmers to express common idioms very succinctly where Delphi takes many lines. Why? "language purity". It's 2017, guys. We don't get hired based on how much code we write -- we get hired to solve problems using code, and the less code we need to write for a given solution, the more we can get done in a given amount of time.

    ReplyDelete
  38. I would like Eric Grange​'s proposed syntax playing together the feature implementation's atomicity property mentioned by Stefan Glienke​ :)

    ReplyDelete
  39. David Schwartz Amen.

    But: with Delphi you can solve problems with zero lines of code by dropping some thing on a form, connecting it via designer and it works! (hey, pssst, the code for that component is thousands of lines of terribly verbose and convoluted code, but who cares)

    Joseph Mitzen Probably it got buried under that walls of texts... TL'DR

    ReplyDelete
  40. Stefan Glienke your sarcasm is actually dead-on and focuses on the bigger issue that's totally missing for us today.

    For working with visual elements, the Delphi IDE was a truly innovative approach at the time, and virtually all visual design tools today use.

    But we're talking about "wiring" between non-visual elements here. What is the IDE doing to help facilitate this kind of work? Aside from LiveBindings, there's nothing. Mitov's OpenWire framework is a much more relevant effort in that direction, although even it's more complex than it needs to be due to limitations in the language!

    The language czars are really our worst enemies at moving programming technologies ahead faster. Once most languages have been codified, they are instantly calcified, and there's incredible resistance to any sorts of changes that might inconvenience anybody with older code.

    Changing the default Delphi string type from ANSI to Unicode was perhaps the biggest language change we've ever seen in the history of the language. It had little if any affect on the syntax, but a huge impact on semantics in ways that the compiler simply could not accommodate by itself. Yet I don't recall a single minute of discussion about it. It was a bitter pill that Management decided had to be swallowed.

    But talk about adding language features to make programmers' lives easier? Bah! NEVER!

    This whole discussion only exists because we're still required to write verbose descriptions in code (ie, words) to tell the compiler to move data from one object or unit to another, typically through some kind of decoupled interface.

    Can you imagine having to write code like this to design digital logic circuits?!

    For 20 years now, digital logic circuits -- the things that our coding efforts are designed and intended to manipulate -- have been designed VISUALLY! When you want to get data from an I/O port to a buffer register, you simply draw a line and fill in some properties. It could be anywhere from 8- to 64-bits wide. You let the "compiler" elaborate on each individual "wire" and determine if the loading factors and timing relationships are addressed properly.

    Why are we arguing about the syntax of a bunch of verbiage when it should be possible to do this by simply dropping visual components on a form that represent different objects and drawing lines between them, much like how DFDs are created?

    There are, in fact, tools that do let you draw DFDs on a canvas and that DO then generate code. Just not C++ or Delphi ... it's SQL in most cases.

    When is the Delphi IDE going to automatically create non-visual components from all of our Class and Record definitions that reside on a new "data palette" , where we can drop them on a canvas and lay out what amounts to a DFD that models the data flows between our own data objects and Delphi Form objects (among others)? At that point, this entire discussion becomes completely irrelevant because the compiler handles all of the wiring and initialization issues for us.

    But, alas, we're not even close to that point. And while every incremental step the language czars make in the language do sometimes move us slowly in that direction, it'll be decades before they take enough steps to make it truly feasible. I do not expect this language or platform to evolve fast enough to get us to the point where we'll see such a breakthrough the way we saw when TurboPascal introduced the VCL and a visual design environment that became known as Delphi.

    ReplyDelete
  41. It has been 20 years now, and there's no hint of anything brewing that will replace coding with a visual designer. Meanwhile, the chips that power the computers we program use visual design tools that were unimaginable back when chip designers used to lay out chips as if they were creating a tiny circuit board by hand in their home workshop. But for more than 20 years, they have not had to worry about drawing individual lines that represent circuit wiring, or even writing descriptions. They just use incredibly wonderful visual design tools created by programmers to fit their needs. Programmers who were not confined to working within a specific design paradigm and syntax for expressing the needs of this particular problem domain.

    ReplyDelete
  42. The lure of the Visual RAD designer is a trap.
    I prefer to have all assignments done in code, and not buried in .dfm files. I really wish I had code completion for event types and anonymous method references, so that I could write

    instance.OnSomeEventMethod :=

    and get a list of fitting existing methods in scope, and options to create a new class method, or - if appropriate - a local anonymous method that matches the type of OnSomeEventMethod.

    ReplyDelete
  43. I'd use a nested initializer function in that case.
    It already works, doesn't require additional variables, supported by debugger, can be passed to other constructor, more readable and as atomic as regular constructor (or does Delphi constructors guarantee consistency for other threads on all platforms?)
    Although there are many features I miss in Object Pascal.

    ReplyDelete
  44. It becomes obvious what the new feature is worth, when all the code snippets showing its inconsistency are deleted without being addressed because those patterns were too Delphish and not enough C#ish

    ReplyDelete
  45. Even though the chance of new keywords appearing is slim, perhaps the cleanest would be something like a keyword using that would dictate that anything on the "left side" within the block would have to be a field, property or method of the instance, and not any other variable or method in scope.

    using car := TCar.Create('BatMobile')
    begin
    // car instance has been created
    Color := Colors.JetBlack;
    MaxSpeedKm := 300;
    end;

    That would deal with referential integrity, and handle side effects as they happen today.

    In my dreams, it could also be expanded with

    using car := TCar.Create('BatMobile')
    var
    kmh:Integer;
    begin
    Color := Colors.JetBlack;
    kmh := 300;
    MaxSpeedmph := kmh * 0.621371;
    end;

    where the compiler would not allow local variables named the same name as something in scope from the instance.

    ReplyDelete
  46. Arioch The "Delphish vs C#ish" is completely uninteresting. The key point is to have code that is safe, effective, and readable.

    ReplyDelete
  47. Lars Fosdal I made my own wrapper classes the set the events internally and then I use callbacks in the view model for things like button clicks, list box clicks, etc

    ReplyDelete
  48. Mike Margerum I'm not sure I see the relevance? The point here is to avoid scaffolding code while ensuring integrity.

    ReplyDelete
  49. Lars Fosdal while it should be so, but there was no any other reason those code snippets were removed. They showcased Delphi-specific features missed in Java and C# and in Java-born Spring library. And had to be deleted because of it.
    There is no abstract isolated efficiency of one single feature. Languages are interconnected sets of features and decision goals. Starting with assignment difference in C and Pascal.
    However if you take feature natural for C# and its other features, then cloned it would blend nice into C# mirrored features of Delphi, thus to promote it it makes sense to focus on that subset and to quell mentioning other features, not blending.

    ReplyDelete
  50. Arioch The​ Who's snippets was removed ?

    ReplyDelete
  51. My Delphi code snippets. At least my phone no longer shows them to me since morning.

    ReplyDelete
  52. I did not say you did, or someone else. Anyway Google would not let me reliably know who did.

    I just find this kind of marketing funny. Crafty way to get people sold on proposed features :)

    ReplyDelete
  53. Arioch The are you sure posted it? Can't see any comment in my email copy of the thread.

    ReplyDelete
  54. Well, from this very my phone, which can no more see them, i was posting them. And yesterday night i did saw them, on the phone.

    And the very first comment in this thread, while having no Delphi code in it, was by me, and it was plussed by some other guy. It is deleted too. So at least one of my comments was publicly visible.

    What about the rest, i don't know internal Google mechanics, maybe it had means to hide flagged posts from e-mail too, or they were deleted before e-mails were dispatched, or anything. I don't know.

    For example in my gmail there are only four letters on this thread: three your comments where you explicitly mentioned me and the main post editted by topicstarter notice, and nothing more.

    Not that this "what google can do" hypothesizing seems relevant anyway. Whatever.

    ReplyDelete
  55. Arioch The My post, my rules, simple as that. If you contribute to the discussion that is fine, many people do that. Even if I don't share their opinion or like their suggestion for the syntax. If you however clearly miss the rules, come up with complete nonsense code that clearly would not work according to the constraints defined or simply post to bash on this idea might come from then I don't waste my time and energy arguing about it but simply remove it so it does not clutter up this up to now very constructive conversation.

    If you have some input to this topic, feel free to comment. If you have other suggestions to improve the language then feel free to post them in a separate conversation.

    ReplyDelete
  56. > My post, my rules, simple as that

    Sure. Did not question your right to sell your proposals however you like..

    > Even if I don't share their opinion or like their suggestion for the syntax.

    That is it. They should support your proposal, they are allowed to make minor facelifts to it. But that is all.

    They are allowed to offer slightly different syntax for your proposals, but to do it they should start with supporting your proposal.

    Q.E.D.

    > If you however clearly miss the rules,

    No one can "clearly" missed rules that were never worded less so published.

    > come up with complete nonsense code that clearly would not work according to the constraints defined

    Translation: if you come up with code demonstrating how this proposal decreases Delphi lanuage continuity and introduces irregularities and inpredictabilities into the language core, that is prohibited.

    Q.E.D.

    > If you have some input to this topic

    Translation: If you have ideas how to slightly tune my proposal, at no more than syntax sugar level, while never questioning its essense on any major theme, then i would allow it to happen.

    Q.E.D.

    Obviously i do not want to pretend i am am fan of this proposal, and since cryticising it below syntax sugar level it is prohibited i have to pass.

    ReplyDelete
  57. Arioch The I am looking forward to your lengthy and elaborated post about language improvements to the object pascal language.

    ReplyDelete
  58. Another variant:

    Car := TCar.Create('Tezlike')
    .AmpHours := 150;
    .Color := TColor.clSilver;
    .FastCharge := True;

    But having another Create in there is a challenge.

    ReplyDelete
  59. TCar.Create is a function returning reference to the object.
    TCar.Create(...).AmpHours := 150 is changing the property of just created object.

    ReplyDelete
  60. and so you have one doubled assignment statement ( .... := .... := .... ; ) and a bunch of disconnected ".Name := xxx ;" statements;

    ReplyDelete
  61. A while back I created some code that supported something like this with TForms:

    TMyForm.Create(self).SetGetProps
    .SetProp( 'ClaimNum', aClaimNumber )
    .SetProp( 'CustID', aCustID )
    .SetProp( 'ItemsPerPage', 20 )
    .ShowForm // calls ShowModal
    .GetProp( 'SelectedItem', lclItemIndex )
    .GetProp( 'ItemType', lclItemTypeStr )
    .CloseForm;

    The GetSetProps method injects a process into what's normally just a constructor call. It lets you use the "fluent style" of coding to initialize local properties without changing the existing ctor or overloading anything in the class. Then there's a "DoSomething" method, called "ShowForm" here, that calls ShowModal (which is specific to forms, of course). And finally it lets you extract results from properties without having to worry about back-door access to a potentially dead object. The last method, CloseForm, calls Free after doing its own clean-up.

    And ... Look ma! No 'with' statement is required!

    The SetProp and GetProp methods take two args: the first is the string name of the property you want to get or set; the second is a value (for SetProp) or a local variable that is assigned a value (for GetProp).

    You could also call GetProp prior to DoSomething to extract intermediate results during setup, although a callback method would probably be more appropriate for that. You could also use SetProp to inject anonymous methods for obvious needs.

    The problem I ran into was that it had a subtle (to me) and unexpected issue with tasking collisions that seemed to occur somewhat randomly. This is specific to TForm and the VCL, I think. (I never fully resolved the issue.) But it highlights the need for some kind of state persistence at some level that treats this like a single "transaction" of sorts.

    In the bigger picture, TForms are just objects with some predictable behaviors and routine needs. However, this general pattern is used by most objects where you create an instance, init zero or more internal properties, do something, then get back zero or more results before freeing the object.

    I think this approach is nicer, more elegant, and far more consistent than the equivalent code needed to do the same thing normally. (I've observed around a dozen different ways people implement this particular idiom in Delphi, and most of them are pretty ugly.)

    The challenge with objects like forms is that the behavior of the TForm object is not totally symmetric between the Create and the ShowModal call (ie., the "do something" part) vs. the return from ShowModal to Close (or Free). You have to configure the internal form settings to work properly with this interactive pattern which apply different in different use cases. Dialogs, notifications, selection boxes, and general forms are slightly different and can't all use the exact same logic, even though from a technical standpoint they're all basically doing the same thing with the same type of object (TForm).

    While these issues regarding symmetry are specific to TForms, they could also apply to complex objects of any kind that are created by users.

    So having a solution built into the syntax and semantics of the language that implement this common usage idiom would probably present a far more robust and consistent approach than an ad hoc bunch of methods added to each class to accomplish the same sort of thing.

    I believe it would have to be implemented as a new kind of "meta" interface on top of TObject (or just any class definition) that manages injection of initial values, triggering of a DoSomething method, extraction of results, destruction of the object, and a certain level of atomic persistence throughout the entire "transaction".

    ReplyDelete
  62. That's the thing: it's more of a dynamic flow pattern than just syntactic sugar. There's a definite temporal quality to this idiom that requires some kind of state persistence beyond what's normally associated with a normal class definition. Which is probably why there are a dozen or more different ways of approaching it just with forms.

    ReplyDelete
  63. Stefan Glienke. I see as well a clear benefit in such a smaller language enhancement. As well we should try to keep it similar to the logic of other more leading languages.

    C#
    MyClass my = new MyClass(paramValue1, paramValue2) { ID = 1000, Text = "SomeText" };

    TypeScript
    var my = new MyClass(paramValue1, paramValue2, { ID: 1000, Text: "SomeText" }); // not fully sure as I have not found a combined example of a parameter and property setting.

    Delphi
    my := TMyClass.Create(paramValue1, paramValue2) begin ID:= 1000; Text:= "SomeText"; end;

    not that sexy and compact but logical '{' -> 'begin' and '}' -> 'end'.
    Actually a combination of "anonymous" & "implicit with". This would allow to work nicely inline and behave as younger programmers would expect.

    ReplyDelete
  64. David Schwartz The drawback with string constants is that if you refactor a property name, the string constant is not changed and it breaks.

    ReplyDelete
  65. David Schwartz I would like to add +1000 on Lars Fosdal objection to your code. You are trading compile time error with run time one. While there are certainly cases where that is acceptable trade off, in this case it is not even close to acceptable.

    I would rather shoot myself in the foot using current 'with' implementation than doing that using string name to set properties in code.

    ReplyDelete
  66. Günther Schoch The syntax you propose was my original idea which we found out is very likely to introduce defects in the code while the syntax Eric suggested does not. It still is there in my post but striked out. The with even makes it really pascalish in the sense that it almost reads like a sentence: "create a car with these properties set to these values"
    Copying exactly what curly bracelet languages do will not work in Delphi.

    ReplyDelete
  67. Lars Fosdal Dalija Prasnikar I posted that as an example of a solution I came up with that works using existing technology. You need SOME way to identify the properties in this case. I'm not defending the use of strings. If you don't use symbolic constants, then you need to alter the language to support the necessary constructs.

    ReplyDelete
  68. Stefan Glienke thank you for the clarifications. I hesitate to agree with the "with ... end;" syntax. I would be IMO the first syntax where begin" and "end" are not balanced. In that case I would prefer you alternative suggestion

    my := TMyClass.Create(paramValue1, paramValue2; ID: 1000; Text:: "SomeText");

    keeps the whole compact, readable and distinct. And actually clear for a pre-compiler to translate it to "my.ID = 1000; ..." I do not see any drawback.

    ReplyDelete
  69. Günther Schoch try/finally|except/end case/end class|interface|record/end

    Just saying.

    I am getting the impression you did not even completely read my original post. The syntax with the colon was already suggested.

    ReplyDelete
  70. David Schwartz Indeed we do need to alter, enhance and improve the language - but these days that seems like mission impossible.

    ReplyDelete
  71. David Schwartz I believe the whole point of this post was to discuss language enhancements as in "what language currently does not support but it could be added to the language and what would be the best syntax to do that."

    In that light justifying bad sides of your "solution" with existing technology constraints is kind of pointless.

    ReplyDelete
  72. Stefan Glienke I just wanted to focus that "with end;" does not exist.

    >... not reading ...

    I was. But sorry about my misspelling in the previous answer. I tired to say.

    "In that case I would prefer youR alternative suggestion"

    For me it looks just more intuitive and modern than the "with syntax". And I do not see any drawback beside the fact that EMBT will unlikely implement any language changes (other story).

    ReplyDelete
  73. Günther Schoch I like the with syntax more. It fits more into the overall scheme of pascal although I am not the type of person that needs everything "pascalish" and with verbose keywords.

    However the "with" syntax clearly distinguishes ctor parameters from properties being set afterwards and is still compact enough imo. Putting both things into the ctor parameter list might make it hard to read (as parameters and those additional properties to set are just differing in one character).

    ReplyDelete
  74. for car := TCar.Create
    do begin
    end;

    :P

    ReplyDelete
  75. Stefan Glienke OK - I see your point. I could live with both solutions. Most important that there will happen something to develop the language.But like the song text of "Lemon Tree".

    "but nothing ever happens, and I wonder"

    http://www.songtexte.com/songtext/fools-garden/lemon-tree-23c2784f.html. :).
    songtexte.com - Fool’s Garden - Lemon Tree Songtext

    ReplyDelete
  76. I personally like the suggestion by +Eric Grange - using the with syntax:

    car := TCar.Create
    with
    Name := 'Chevrolet Corvette';
    Color := Colors.Yellow;
    MaxSpeedKm := 300
    end;

    Although for me the following would even be more "pascalish":

    with car := TCar.Create do
    begin
    Name := 'Chevrolet Corvette';
    Color := Colors.Yellow;
    MaxSpeedKm := 300
    end;

    This syntax would simply enhance the existing "with" syntax with an optional assignment.

    ReplyDelete
  77. Markus Joos I think we should remember the real use case to be able to writing compact code for patterns where we create objects on fly:

    (inline with syntax)
    carList.Add( TCar.Create() with Name:= 'Ferrari'; Color:= clRed; end; );
    (or colon sign)
    carList.Add( TCar.Create(Name: 'Ferrari'; Color: clRed );

    ReplyDelete
  78. With can be an expression returning values. Like if-then-else being ternary operator in Oxygene and many other languages

    X := with TClass.ObtainInstance do
    Prop1 := 1; end;

    ReplyDelete
  79. However at the call site when i call c := TFoo.CreateXYZ i do not know if the function called was an immediate constuctor or a class function. It is black box i use to obtain the instance. I should not make guesswork about black box implementation details. So for the language consistency this inline with should work here too.

    Inst := TFoo.CreateXYZ;
    Inst.CreateABC;

    While not typical pattern still a legit way of calling constructor. The same as calling inherited constructors. Will it work with inline with consistently?

    What about timing w.r.t. AfterConstruction ?
    TForm.Create(nil) begin OnCreate := Self.FormCreate end; will fire the event or not?

    ReplyDelete
  80. Arioch The "i do not know if the function called was an immediate constuctor or a class function" Yes, you do. A constructor is marked as such. If you allow such syntax for functions returning an object you need to consider many other things. That is why I would restrict it to a ctor.

    Timing of the AfterContruction call is an interesting question. If this feature should contain some value and possibly eliminate all sorts of ctor overloads for different parameter combination it might be a good idea to call AfterConstruction after. However iirc currently if AfterConstruction has to be called is determined by a hidden flag being passed to the ctor being called so that needs to changed so the ctor does not call it but its called after the additional initialization.

    Could this cause code defects if you are using something in your property setters being called that is created during AfterContruction? Yes. But it would be the same if those were parameters on the ctor. It would differ in behavior though from creating instance, assigning to variable and then setting the properties.

    As for the with returning something - I thought about that as well but that would look like the with we already have with all the scoping issues. I am not sure if that same syntax with different rules should be used.

    ReplyDelete
  81. Instead of adding specific compiler feature for object initialization, it would better if language introduced general approach for type safe builder pattern. It could be implemented in similar way as it is implemented in Kotlin: https://kotlinlang.org/docs/reference/type-safe-builders.html. This feature is called function type with receiver. One possible way to implement it, would be to extend anonymous methods in Delphi, e.g.:
    type
    TFuncWithReceiver = reference to function([Receiver] arg: T): T;

    In this case, if annotated with Receiver, the implementation body of this anonymous method could refer to T optionally as self and there would be no need to provide this argument when writing inline method implementation declaration.

    You could then introduce a class helper for TObject:
    type
    TObjectHelper = class helper for TObject
    function Apply(funcWithReceiver: TFuncWithReceiver): T;
    end;
    //implementation
    function TObjectHelper.Apply(funcWithReceiver: TFuncWithReceiver): T;
    begin
    funcWithReceiver((T) self);
    Result := self;
    end;

    Usage could be something like this:
    var
    person: TPerson;
    begin
    person := TPerson.Create('id').Apply(
    begin
    FirstName := 'foo'; //can use without self
    self.LastName := 'bar'; //can use with self as well
    end);

    This way, developers could not only use such functionality when initializing new objects, but in many other ways as well.
    kotlinlang.org - Type-Safe Groovy-Style Builders - Kotlin Programming Language

    ReplyDelete
  82. One more question i remember now. What about calling methods?
    One can ho changing top/left/width/height for the control
    triggering realignments after every call, or one can just call SetBounds( L, T, W, H ).
    Delphi doed not allow to set several variables with one assignment statement. Then this proposal seems to stimulate use of most overheaded ways of setting properties.

    ReplyDelete
  83. Linas Naginionis​ this approach only works for GC-based and ARC-based languages. In Delphi it can work on interfaces, not on objects.
    It ist he classic gotcha of trying tp apply "fluent api" to objects.

    X := TFoo.Create.SetProp1(value1).SetProp2(Value2).Apply(function1).Apply(function2)

    As soon as function1 throws an exception we have a memory leak - object instance created, but not assigned to any variable and essentially lost for good.

    This approach maybe can be hacked around if the receiver passing by would be Advanced Record with interface-wrapper over the object and implicit-operator to cast back to object. However it would still be fragile. As soon as any other method would be chained except Apply - you are in the swarm. And how would you explain the rules what can and can not be chained to the novices not into low level specifics of objects and interfaces?

    ReplyDelete
  84. Arioch The I know you can come up with other examples that don't have an immediate solution but fwiw you can already write:

    BoundsRect := Bounds(L, T, W, H);

    Anyway I think such syntax would be more help- than harmful in terms of what code it enables to write. Like with almost every programming language feature.

    ReplyDelete
  85. Arioch The I don't think this approach works only in GC-based languages. You can change the implementation of Apply method to catch exceptions and free the object in that case. The main point is, that developers can write different use cases by themselves, no need for additional changes in compiler.

    ReplyDelete
  86. Linas Naginionis I think your proposal introduces the same scope ambiguity as with does.

    ReplyDelete
  87. Stefan Glienke Why do you think so? The compiler should emit compiler error if multiple [Receiver] attributes are found in the anonymous method type declaration. Note that I've proposed to mark the receiver type using a dedicated attribute doesn't mean that it cannot be implemented in some other way. Attribute just helps to avoid adding new keyword to the language.

    ReplyDelete
  88. Point is you can write any code into that anonymous method. Add a property to TPerson that has the same name as something already used in that anonymous method, boom, scope clash, behavior changed, just like with does.

    ReplyDelete
  89. Stefan Glienke Could you explain how this could happen with the example Apply method I've just provided?

    ReplyDelete


  90. var
    person: TPerson;
    something: string;
    begin
    person := TPerson.Create('id').Apply(
    begin
    FirstName := 'foo';
    self.LastName := 'bar';
    something := 'foobar'; <- captured variable
    end);

    Now someone adds property something to TPerson.

    ReplyDelete
  91. Stefan Glienke But the same rule applies for any anonymous method/lambda, it is expected behavior and you should be aware of it. I wouldn't call it a defect, it is a feature actually.

    ReplyDelete

Post a Comment