Continuing the theme of recent – and upcoming – posts about new (and not so new) syntax in modern (and not so modern) variations on the Pascal language, I just have to comment on what I regard as yet another stunningly good job that the guys at RemObjects have done in their “Nougat” flavoured Oxygene. Specifically in relation to how they have implemented the named method parts syntax in Objective-C.
The example that Marc Hoffman provides in his recent blog post, is the constructor method for a string, <a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html" target="_blank">stringByPaddingToLength:withString:startingAtIndex:</a>
.
This would be called in Objective-C thusly:
[myString stringByPaddingToLength: 9 withString: @"." startingAtIndex:0];
How on earth can you contrive to make such a call in a Pascal derived language ?
Well, as Marc describes, there are a number of possible approaches, most of which are dictated by whether you are extending the language or merely providing a bridge from the language into the runtime.
The bridge approach results in the most cumbersome – and imho downright ugly – solutions but remains strictly and technically within the limits of the existing Pascal language.
A first cut at providing a bridge to this Objective-C API might simply ignore named method parts by implementing them as simple parameters to the base method name, overloading where necessary:
myString.stringByPaddingToLength(9, '.', 0);
Or you might encode the method part names in the method itself either by preference or by necessity (if there are methods whose method parts differ in name, but not type – though I am not as yet away of any such examples in the Objective-C runtime):
myString.stringByPaddingToLengthWithStringStartingAtIndex(9, '.', 0);
Not Your Daddy’s Pascal
Whilst both of these are demonstrably “Pascal”, within the existing syntax of the language, neither maps very comfortably or intuitively to the Objective-C runtime.
Extending the language, by contrast, creates something that is arguably not Pascal at all – by definition, since it involves changes or additions to the syntax – but which is, ironically, the most Pascal-like in terms of preserving elegance and clarity not only in the language but in way that it exposes the underlying RTL:
// [myString stringByPaddingToLength: 9 withString: @"." startingAtIndex:0]; myString.stringByPaddingToLength(9) withString('.') startingAtIndex(0);
imho, not only is this cleaner code and closer to the underlying RTL, but it is, if anything, an improvement even on the “native platform” code for that RTL!
Whilst some might rail at this, crying “But this isn’t Pascal!!“, I would ask whether this would be considered more or less “Pascal-ish” than:
TMyWindowClass = class(TMyBaseControl) procedure WMPaint(var aMessage: TWMPaint); message WM_PAINT; end;
Colour Me Excited
For myself, I am increasingly excited to see how RemObjects approach targeting new platforms with the language that I am most familiar with, in the same way that I was excited when I first saw Delphi 1.0 and how the language was adapted to suit Windows, back in the day.
Replacing spaces with dots results in:
myString.stringByPaddingToLength(9).withString(‘.’).startingAtIndex(0);
There also exists a named arguments syntax:
myString.stringByPaddingToLength(9, withString := ‘.’, startingAtIndex := 0);
This syntax is only available for Variants in Delphi, but indeed, it looks pretty Pascalish and it’s an ordinary thing present in Ada from the beginning
Wouldn’t replacing spaces with dots causes problems with you have a method – having a single parameter – that returns some other object ?
I’m not sure that example makes it all that clear, but it seems a pretty obvious problem of ambiguity in the syntax.
The named arguments syntax is an interesting alternative but in trying to use the ‘:=’ ends up looking confusing imho. The parameters aren’t assignments, just labels for values/expressions. The more “correct” (all subjective, I know π ) notation would surely be the same as reflected in record constant initialisations:
But somehow the Oxygene notation looks cleaner to me. Perhaps it’s the use of parentheses, giving a more consistent, “self contained” look to each parameter, instead of the jumble of commas and colons in the alternative ? [shrug]
I’d give it “implicit record” notation.
Methodname( (field1:Value1; Field2: Value2) )
This however makes redundant parenthesis.
And Pascal probably does not have concept of undefined parts of record, while Objective C might have it (or not? dunno. Can i make a call without specifing all the params ? In VBA i could)
Indeed, as already Jolyon pointed out, using dots to concat the parts of the method call would be highly ambiguous, and thus not feasible.
We did explore a wide variety of options, including the second “named parameter” approach that you suggest, but our main issue with that one was exactly that: these ARE NOT named parameters, and that is the wrong way to think about these names.
All parts together form the core name of the method. If you look at the docs, or into the implementation details of how a method call is encoded, you will see that the name, literally, is “stringByPaddingToLength”withString:startingAtIndex:”. The first part is indeed no more important than the other parts β and we wanted a syntax that properly reflected that.
Then you don’t need delimiters at all. If it is all the single name – then be it.
Object.MethodX(11)withOption(‘ABC’)havingVoidFlag()overSource(DataSet1);
There is a balance to be struck between what the compiler strictly needs in order to make sense of the code and what the humans writing that code have to live with.
But, I wonder whether the spaces in Marc’s example are in fact necessary. Whitespace typically isn’t. Since the values of each method part are clearly contained within the parentheses following the part name, the whitespace separation may indeed be purely aesthetic.
But I personally would still want them there for myself, even if the compiler doesn’t insist that I put them there. π
Maybe Marc could clarify if he’s reading this .. ?
Data(Table)GetField(name)IfFound(Key, Value)Else(Default)
Now we put it – with whitespaces – into if-then-else and get ambiguity.
Same with while-do.
Same with repeat-until.
Even if you can have “Else” as a method part name in Objective-C, my supposition is that where a method part name collides with a reserved word in Oxygene then there will be some form of escaping possible.
The guys at RemObjects are pretty darned clever and know far more about this stuff than I do. I’m certain they have this all well and truly covered. π
indeed, you don’t need the spaces.
I guess I’d have preferred : mystring.StringByPaddingToLength(9) & withString(‘.’) & startingAtIndex(0)
that is, an actual operator character but NOT A DOT, instead of Space being used to combine the parts. Then the “selectorBegins(arg) & selectorContinues” is visibly more different. As it is, it looks like a typo.
Especially when you format it on multiple lines, and then someone gets confused and starts adding semicolons. Pain. Pain. Pain.
W
People can get confused and start adding semicolons in even the simplest code. Only yesterday I came across the following:
See the problem ? π (not my code, I hasten to add π )
Formatting on multiple lines shouldn’t be any more confusing than in any other such case. At least not if you indent-align the parameters in a way that makes them obvious as such:
Still looks like a typo with spaces to me.
In Objective-C the syntax is actually cleaner IMHO than the Nougat variant, because the whole call is neatly delimited with [ ], which makes it obvious it’s a single entity.
Named arguments as Ivan listed is neat, I don’t find the use of ‘:=’ confusing, as you’re effectively assigning a value to parameter, while your version with just ‘:’ looks like a declaration (which it isn’t), so is confusing.
Warren’s use of an explicit non-space character is neater than Nougat’s too, as it makes what is happening obvious, though ‘&’ is already used to escape reserved word names, and thus couldn’t be used for that purpose, but ‘:’ could serve there (if it wasn’t already in Oxygene for nullable types)
+1 π (I agree)
Dunno why they chosen to extend language with those spaces looking kinda infix notation, when they could just use their colon operator and do fluent-style.
“(if there are methods whose method parts differ in name, but not type β though I am not as yet away of any such examples in the Objective-C runtime)”
Objective-C does not support method overloading my parameters β i.e. the same method signature and name, but distinguished by different types passed to it β but Objective-C APIs will quite frequently do the oposite β i.e. have variations of the same method with different descriptions for what the second and further parameters do. for example, consider these two delegate methods of NSURLCopnnection:
– (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)aResponse
– (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
Aha – thanks for the additional info Marc. π
The problem arises due to the fact Objective-C distinguishes methods by parameter name whereas Delphi (and, for that matter, C++, Java, C# etc.) by parameter type. Nevertheless, in most cases, there’s no need to force a special syntax as there isn’t a clash, partly because the name of the first parameter in an Objective-C message is not part of its signature.
That said, a special syntax is still ultimately needed for when clashes do happen (using type aliases in XE2/XE3 is a bit of a hack). Personally I quite like FPC’s approach (though I dislike the apparent enforcement of the special syntax even when it isn’t needed). I don’t care for your point that the message syntax is a Delphi-ism not Pascal-ism, since I’ve never used a pre-Delphi Pascal derivative and never will do.
> I donβt care for your point that the message syntax is a Delphi-ism
> not Pascal-ism, since Iβve never used a pre-Delphi Pascal derivative
> and never will do
More than a Delphi-ism, it’s a Windows-ism. And that’s the point. Just as in this case you have missed the point, re-inforced by Marc and that I tried to emphasis in my commentary (i.e. the post itself) that these are not parameter names but parts of the method name.
Devising a syntax based on the idea that these are parameter names results in something that doesn’t actually reflect what is going on in the environment.
The message syntax embodies that tightness of fit between environment and language that I believe is what is similarly reflected in the approach being taken by RemObjects. π
“Devising a syntax based on the idea that these are parameter names results in something that doesnβt actually reflect what is going on in the environment.”
indeed. sooner or later, you’re gonna wanna have to β say β use a selector (ie the “SEL” type), and then things would get really confusing if the language pretended that the first part of a method name is something different/more special than the other parts.
“sooner or later, youβre gonna wanna have to β say β use a selector (ie the βSELβ type), and then things would get really confusing”
You’re making it sound complex when it isn’t. If you want a raw SEL using a language that doesn’t intrinsincally support the Objective-C object model, then just make a single Objective-C runtime call:
SelToUse := sel_getUid(‘myTeam:isBetter:thanYourTeam:laLaLa:’);
What’s so hard about that?
nothing is hard about that. neither is using assembly.
but the whole point is that this is a language that DOES intrinsically support the Objective-C object model.
> More than a Delphi-ism, itβs a Windows-ism.
> […]
> The message syntax embodies that tightness of fit
> between environment and language…
Er, I take it your love affair with FPC was more one night stand than decades-long romance then…? I had read your post as lightly criticising FPC’s approach to the same problem, which is to use a variant of Delphi’s ‘message’ syntax.
I haven’t got that far with FPC yet. It wasn’t a one night stand but there hasn’t yet been a second date, and I wouldn’t even say I got to first base. π
But I am on nodding acquaintance with the message variation in FPC for Objective-C bindings. It has some benefits over the Oxygene approach, most specifically in the area of dealing with collisions of names in Objective-C that cause problems in Pascal, by introducing a level of indirection between the implementation (the Pascal code) and the Objective-C exposure of that implementation.
A concern I have with the FPC approach (based on perception, not experience) is that this indirection only seems to exist in one direction: when implementing Pascal code to “fit” in the Objective-C environment. Objective-C code that is exposed to the Pascal side is not handled in a similar fashion.
So on the one hand to provide a Pascal object that responds to an Objective-C message you can write:
myFoo(aValue: Integer; aBar: Boolean); message "myFoo:settingBar";
but on the other hand – aiui – when sending a message to an Objective-C object you would have to write:
obj.myFoo_settingBar(42, TRUE);
which, apart from anything else has the nasty contradiction of going to the lengths of preserving the method part name but at the same time moving it so far away from the value (parameter) it relates to as to make it next to useless (this is particularly the case in longer method part lists, obviously).
I agree with your last point – in a different way, that’s what I was referring to when I said ‘I dislike the apparent enforcement of the special syntax even when it isnβt needed’ in my original comment. IMO, that syntax should only be required as a way of untangling actual ambiguities, which by no means happen all the time, at least when using the core Cocoa libraries.
More generally with FPC’s Objective-C support, while I can see why the developers did it, I’m not sure about the objcclass thing. Ultimately, the best solution for FPC (and Delphi) IMO would be to avoid a dual object model by building Objective-C messaging support into TObject, similar to how support for Windows messaging was partly built into TObject in D1. Fundamentally, having parallel object models is just messy…
> Fundamentally, having parallel object models is just messyβ¦
Yep, and this is what has been avoided in Oxygene. π
I find it interesting that between them, Delphi, FPC and now Oxygene have covered the spectrum of possibilities…
what’s more,
myFoo(aValue: Integer; aBar: Boolean); message "myFoo:settingBar";
is something that someone has to define somewhere. it’s a mapping. a bridge. As my blog post explains, Nougat is not a bridge. it directly consumes and participates in the Objective-C runtime.
there is no mapping that someone write that maps “stringByPaddingToLength:WithString:StartingAtIndex:” to a method that gets exposed to Oxygene. there’s only “stringByPaddingToLength:WithString:StartingAtIndex:” itself, as exposed by NSString.
the FPC syntax is a hack, to map something that FPC cannot really handle, Objective-C objects, into FPC object space.
that’ fine.but that’s not what Nougat is, and it’s not how it works.
Is Objective C limited to one parameter after each name part ?
IOW may there be myFoo(aValue: Integer; aBar: Boolean; aBaz: Integer); message “myFoo:settingBar”
that could mean to ObjC side any of
myFoo(AValue) settingBar(aBar, aBaz)
or
myFoo(AValue, aBar) settingBar( aBaz)
?
The thing to get your head around is that they aren’t really parameters as such but separate pieces of the method name. Each part of that name may have a value associated with it when sending the message.
But yes, to put it in your terms, each ‘parameter’ has one value and only one value so you can’t do either of the two things you ask/suggest. And I don’t think you can omit them either, or even change the order of them, because they are not named parameter values but part of the method name you are calling… leave out a part or change their order and you are calling an entirely different method that may not even exist. π
Think of it as the difference between calling
StringToDateTime
andDateTimeToString
, rather than changing the order of parameters identified by name.What you can do however is have an unnamed part. But it’s still a part.
i.e. a class might implement a message
setOrigin::
which would be called thus[shape setOrigin:10 :10];
. As I undertand it, omitting the name of the part is not a question of part names being optional in any/every case. In this case the name is not identified when sending the message because the part is not itself named by the receiver of the message.I’m not sure if Oxygene will even support this (it’s a pretty bad idea for obvious reasons), but if it does then I guess it would look something like
shape.setOrigin(10) (10);
.This is quite a good reference for this material:
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html
If you’re anything like me, you’ll initially be turned off by the syntax but then when you understand what it does and why you will begin to appreciate it. π
> but if it does then I guess it would look something like shape.setOrigin(10) (10)
I guess this illustrates an ambiguity pitfall of the Nougat syntax.
The above code already has meaning in Pascal if shape.setOrigin(10) returned a function pointer or delegate, as it would be understood as a call to that function pointer with 10 as parameter.
The Objective-C syntax doesn’t suffer from this ambiguity, as the call is enclosed in between [], so there is a clear distinction between a multi-named-parts method, and two chained calls, both for the compiler and the humans reading the code.
For a non-ambiguous syntax, either the call needs to be wrapped as a whole (as in Objective C), or a “method name chaining” operator be introduced, to differentiate it from call chaining. And that operator can’t be ‘.’ for a similar ambiguity reason.
Well, I was only guessing what the Oxygene syntax would be. For the reasons you give the syntax might be different in such cases or it might not even support such messages (they are generally frowned on and probably won’t be found anywhere in the Cocoa framework aiui) even though the Obj-C language allows them.
Simply defining an interposed character as a place-holder for “anonymous” method parts would quickly and easily solve the ambiguity, ‘&’ or ‘@’ or anything that isn’t – on it’s own – a valid identifier really. Having just come up with the notion, I have to say I like the idea of ‘&’ as it can be read as “and”, which is quite natural since it identifies a further named part value in addition to other perhaps named parts:
shape.setOrigin(10) &(10);
Problem solved I think. π
(Marc, if that really does solve a problem you hadn’t yet got around to addressing, feel free to adopt it. No Charge) π
Well, you are wrong about my terms. Read again, “parameter after each name part” π
So your “value” is my “parameter”.
Thanks for clarification.
Aha yes, I see that now. π
> shape.setOrigin(10) &(10);
Then simpler to be shape.setOrigin(10, 10);
With limiting condition, that anonymous params goes 2,3,4… not 1,2,3…
Which would be natural for human reading (relating name part to 1st parameter)
Defining syntax to most “naturally” suit a language feature that is rare to the point of hardly ever being used doesn’t seem like the most sensible approach to me. π
shape.setOrigin( value, value );
Whether labeling/naming the values or not makes it look like the values are “parameters”, separate to and something other than the method name (outside and leading the parentheses) when the labels/names for the values inside those parentheses – in this syntax – are just as much a part of the name and are not something “other” at all.
In the Oxygene syntax this is quite apparent. The things that are “other” are the values passed to the message, and these are the only things that are parenthetical. Everything outside the parentheses is the method name.
Anything else is struggling – manfully – to try to shoe-horn Obj-C semantics into existing Pascal grammar.
Someone approaching the task in this way might even succeed in this small area, but having given ground in that battle they will almost certainly end up having to make other, less palatable compromises in other areas, leading to a mess that could have been avoided. π
‘&’ is already used to escape reserved keywords, so a lone ‘&’ could indeed signify an empty identifier.
That said, I can’t shake the feeling that this is weakening the syntax, and could have future unforeseen side effects. The risk with weakened syntaxes is that more and more random stuff in the code takes meaning from a compiler POV, which, like in C, increases the risk of typos being accepted at compile time.
After all, in Objective-C they could have dispensed with the surrounding [ ], as from a compiler POV it could be resolved, and the ambiguity that would have arisen being not really worse than those of the Nougat syntax. Yet they didn’t.
“in this syntax β are just as much a part of the name and are not something βotherβ at all. In the Oxygene syntax this is quite apparent.”
No, it’s not apparent at all.
It’s apparent in Objective-C (due to the enclosing), in the Oxygene syntax it looks like an operator or character was forgotten, f.i.
whoopsy(0) doItWith (thisMethod(1) does(2) + whatever(3) itWants(4) * simpleFunc(5))
The current FPC approach is hardly a ‘bridge’ because support is found in a *third* class type, instances of which you define using the ‘objcclass’ keyword (cf. ‘object’ for TP-style object types and ‘class’ for Delphi-style classes). It isn’t ‘hacking’ something onto the ‘FPC object space’ – it’s adding a new (third!) ‘object space’. The fact the syntax for this new ‘object space’ isn’t very elegant is another issue…
Separately, the old FPC Cocoa bridge was rather more basic than the Delphi one, so I don’t think it’s fair to lump the two together. In particular, for each Objective-C class wrapped, you had to manually provide method implementations that called into the Objective-C runtime. In Delphi, contrast, you only have to declare a suitable interface type and (optionally) pre-declare an instantiation of the generic factory class.
Yep – FPC dropped the bridge approach, as you say.
The current FPC approach and the Delphi one are basically the same. The only difference is that the Delphi approach uses generics and RTTI to generate at runtime the code that FPC requires you to create up-front. This may be more convenient but whether it is more efficient, effective or sufficiently robust is – much like the syntax differences – another issue. π
It’s a message …
>procedure WMPaint(var aMessage: TWMPaint); message WM_PAINT;
message in this context is an ‘abuse’ of the well defined term in 00. Method call in the ‘C’ world is a function call + dynamic address lookup. More or less a response to the time consuming string compare, simply a technical necessity. In practice the string compare did not hurt – a rule of thumb suggests, have 4 levels of inheritance and a balanced inheritance tree. The string lookup had been replaced by an ‘integer’ lookup at runtime.
In pure OO an object asks the other via messages. In the C++ world the OO world is turned around 180 degree in most cases, not only but also because of the ‘.’ operator. The ‘.’ operator sent a wrong message to the developers, used to procedural languages, to see the class as summary of functions. COM/ActiveX was not very helpful, it made things worse from this perspective.
No, “message” in this context was specifically an implementation detail of the Windows platform. “Pure” theoretical definitions of OO matter little when dealing with the concrete details of a specific OO implementation.
In the OO in ObjectPascal/Delphi, objects respond to method calls.
In Windows, windows (little ‘w’) respond to messages.
In Delphi, where an object represented a window then the language was extended to provide a binding to an RTL/VCL mechanism that eliminated the need for cumbersome and very “C’-like switch/case monster cookie cutters for message handling, and replaced it with something declarative that wasn’t any form of previously recognisable “Pascal” but which was very “Pascally” in intent and result.
Yes. message imo. has a lot to do with message maps. Windows (C++) compilers have always been special.
Mhhhhhh, partially. You will run into the differences soon when you look into the class libraries that ‘origin’ from more Smalltalk like implementations. Dependent on where responsibility is located, dependent on the way the design is done. Who is using CRC cards still?
In practice, I agree it is better to stay accordant to common philosophy behind a framework and to integrate instead of putting something on top that is maybe very nice but not easy to understand and not intuitive to those who work on a ‘platform’ on a regular basis.
My only real objection is the space as conjunection syntax – it violates the spirit of the language, and possibly leads to some seriously strange bugs with name space conflicts if a semicolon gets forgotten/deleted.
A non-space conjunction would make me much happier, a(1)::b(2) would tell the compiler they are meant to be together a(1) b(2) could mean I forget a semicolon, and until the right conditions could compile… oddly.
Similar to
if (condition) then
trueclause1;
trueclause2;
looks right, but the compiler does not read indention and trueclause2 always runs regardless of condition. Yes, yes, I am one of those who believe begin/end SHOULD be manditory. (besides, in delphi, begin it changes how the debugger traces an if statement, so it really is in your best interest to use them all the time)
The fact that there is no ‘;’ between them tells the compiler that they are meant to be together.
Any non-space conjunction would only be in addition to any optional [white]space conjunction unless you also make it a rule that conjoined method parts can have no whitespace separation, which would deny the possibility of pretty-formatting calls (line-breaks etc) with long method part lists for readability.
So, you add non-space conjunction but the space conjunction still remains and now you have the possibility that someone might forget the non-space conjunction part and it would still – sometimes – be difficult to tell if it were a missing conjunction or a missing semi-colon.
At some point you have to stop trying to engineer the language to deal with carelessness that imposes overhead on the developer in 99.999% of cases where it wasn’t necessary, and simply ask the developer to please take some responsibility for the remaining 0.0001% cases themselves.
π
And there, folks, is the violation of the spirit of the language I was talking about summed up beautifully.
I don’t understand what you are trying to say with that comment, but I think you are trying to say that I have somehow contradicted myself by justifying some non-spirit of pascal-ish approach where previously I decried other violations of the “spirit of pascal” (as I saw it – whether your idea of the “spirit of pascal” is even the same isn’t entirely clear either).
But, on that basis I would say this…
Pascal – even the long established fundamentals of the language – won’t stop you from being an idiot and making mistakes, as evidenced by the example (from the real world, encountered this week) that I mentioned:
No doubt you could contrive some set of rules that would make this illegal code. You could for example simply make null/empty statements invalid/illegal, there-by capturing that offending extraneous ‘;’ before it does any damage. Of course, in doing so you then disallow some perfectly valid uses of null statements, things which are far more likely to be found useful than the thing you were trying to prevent is ever likely to be a problem:
As I say, at some point the language has to credit the developer with some intelligence and willingness to pay attention to detail, otherwise the language itself simply becomes unwieldy.
The difference is the point at which the language “takes the safety off”. Pascal has more safety-nets than many languages, but it is not – nor could or should it be imho – an entirely risk free language.
I grant, there are some syntax gaps in the language which conflict with its original goals.
This is hardly a reason to add more when a simple alternative exists to reduce the likelyhood of bad code.
Another options: