Eric, > On 15 Aug 2018, at 4:14 PM, Milles, Eric (TR Technology & Ops) > <eric.mil...@thomsonreuters.com> wrote: > Does your requested change amount to setting the "isSafe" condition on all > PropertyExpression instances in a block?
That has been the original idea ages ago, when I have started playing with that. It did not prove quite feasible: you cannot “safe” operators this way, there are those ugly quirks of super?.whatever etc. I have mentioned before... > That is the AST equivalent of adding the '?' to each '.' Quitte, only some of '.'s do not take '?' gently and sport a weird results, from an insane (though understandable) runtime behaviour like “null?.is(null)" sports up through exceptions compile-time to (at least to me not understandable) Verity Error caused by super?.anything. Thanks and all the best, OC > > From: ocs@ocs <o...@ocs.cz <mailto:o...@ocs.cz>> > Sent: Wednesday, August 15, 2018 7:55 AM > To: h...@abula.org <mailto:h...@abula.org> > Cc: dev@groovy.apache.org <mailto:dev@groovy.apache.org> > Subject: Re: suggestion: ImplicitSafeNavigation annotation > > H2, > >> On 15 Aug 2018, at 9:47 AM, h...@abula.org <mailto:h...@abula.org> wrote: >> >> OCS, your reference to Objective-C is interesting, as is your expectation >> for your code to encounter null values often. > > In my experience, a null is (should be, at the very least) a first-class > citizen, value which simply means “there is no object there”. In normal code > it happens all the time, e.g., if you check a map with a key which happens > not to be there, or if you use find with a condition which happens not to > match any of the items in the list, etc. > >> I may be old school, but I think the semantics of null values is >> interesting, as is the semantics of safe navigation. > > Actually, all this NPE stuff (and its consequences) is pretty new-school :) > > The aforementioned Objective C (of eighties!) has a couple of very simple > rules: > - wherever an expression returns an object, it may return a nil; > - whenever a message[*] is sent to a nil, it is a very quick and efficient > empty operation, whose return value is again nil. > > [*] in ObjC, you use an object exclusively[**] through sending messages: you > send a message without any argument for a property getter, you send a message > with one argument for a property setter, you send a message with an arbitrary > number of arguments for a method call, etc. > [**] but for the access to instance variables (more or less “fields”), which > is sort-of similar to plain-C structs, and happens from the declaring class > code only, usually is limited to the property accessor methods and never used > outside of them. > > Whilst I do completely appreciate that tastes and experiences do differ, > well, myself, in thirty-odd years of using this paradigm daily and after an > uncounted zillions source lines in projects some of which have 25-odd-year > lifespan, were made for NeXTSTEP ages ago and are maintained for macOS of > today, I am yet to find any drawback of this approach. Cases when a bug has > been caused by unintended and uncaught sending a message to a nil are > extremely rare (myself I can recall about three of such cases in all those > years), whilst the advantages for code readability, conciseness, and > robustness are tremendous. > >> Safe navigation addresses the matter of nested if statements, perfectly in >> my opinion. > > Absolutely. Nevertheless, there's absolutely no point why it should be > limited to methods — compare e.g., the following case (very simple and thus a > bit artificial to keep concise): > > === > interface TreeNode { > TreeNode createChildIfPossible(String name); // if possible, creates and > returns a new named child; null if not possible > } > void makeSubtreeIfPossible(TreeNode tn) { // here, we do not care whether > successful; just want to make as big a subtree as possible > def foo=tn?.createChildIfPossible('foo') > foo?.createChildIfPossible('bar')?.createChildIfPossible('baz') > > foo?.createChildIfPossible('bax')?.createChildIfPossible('baz')?.createChildIfPossible('last') > } > === > > Thanks to ?. we have dodged the necessity of super-ugly ifs, but still, the > result is sort of at the ugly javaish side. Can we get groovier? Well of > course we can: > > === > class TreeNode { > def leftShift(object) { this.createChildIfPossible(object) } > } > void makeSubtreeIfPossible(TreeNode tn) { > def foo=tn?<<'foo' > foo?<<'bar'?<<'baz' > foo?<<'bax'?<<'baz'?<<'last' > } > === > > Oh, oops! We can't do that, for we do not have “safe navigation” for > operators, and thus, if we want to use the << for adding a child, we would > have to get back to ugly Javaish ifs, which I would not dare to show lest > some reader might get sick :) > >> What's more, it is explicit in terms of where it applies - you can combine >> safe navigation with "unsafe navigation", allowing NPE's to be thrown and >> nulls to propagate where appropriate. > > Agreed, and where one needs just an occasional null-propagation, it's perfect > (or, as the example above shows, would be, if it could be used with all the > operators instead of just with the method call, property access and indexing > ones). > > Nevertheless, there are cases where one needs the null-propagation not just > occasionally, but very often (if not exclusively) in a piece of code — do not > all the ?'s in the examples above look ugly? And even if you like them, they > very definitely make the code highly fragile: it is very easy and quite > probable to simply forget one or two of them, getting thus one unwanted, > uncaught and potentially disastrous NPE (essentially in random based on the > data, so testing might not help to find&fix the culprit). You would rather > have to add a try/catch harness, which in this case creates a boilerplate > code. We should do without boilerplate in Groovy, in my opinion! > > On the other hand, this would be clear, concise, completely > intention-revealing, and completely robust: > > === > @ImplicitSafeNavigation(true) void makeSubtreeIfPossible(TreeNode tn) { > def foo=tn<<'foo' > foo<<'bar'<<'baz' > foo<<'bax'<<'baz'<<'last' > } > === > >> Unless the same explicit control can be exerted over arithmetic / other >> expressions, I think those two concepts cannot be compared. > > Seems to me it's just one concept, not two of them. > > It has been available from the very beginning for two kinds of expressions: a > method call and a property getter. > > Lately, it has been extended to another kind of expression, namely, the > indexing. > > It would be only consistent and reasonable to extend the thing to all > expressions without an exception. > > Regardless whether that happens or not, still, it is only one and the same > concept; we are not debating any other one, but just an extent of its > availability. Especially in Groovy, where... oh, see please below :) > >> I also think any assumption about the semantics of null values are >> problematic. In particular, assuming that any null value in an expression >> should be propagated is not obvious to me. > > To me, it seems very obvious, completely natural and quite intuitive — > especially in Groovy where (unlike many other languages) the “expression > operators” are essentially just a convenience syntax sugar for plain method > calls (i.e., “foo<<bar” is nothing but a convenience shorthand for equivalent > but ugly “foo.leftShift(bar)”, “foo+bar” for “foo.plus(bar)”, and so forth). > > Thanks and all the best, > OC > >> Den 2018-08-15 04:18, skrev ocs@ocs: >>> mg, >>>> On 15 Aug 2018, at 3:26 AM, mg <mg...@arscreat.com >>>> <mailto:mg...@arscreat.com>> wrote: >>>> Fair enough (I am typing this on my smartphone on vacation, so keep >>>> samples small; also (your) more complex code samples are really hard >>>> to read in my mail reader). It still seems to be a big paradigm >>>> change >>> I might be missing something of importance here, but I can't see any >>> paradigm change; not even the slightest shift. >>> The only change suggested is that one could — in the extent of one >>> needs that, which would self-evidently differ for different people — >>> decide whether the “safe” behaviour is explicitly triggered by >>> using the question-mark syntax, or whether it is implicit. >>>> since regular Java/Groovy programs typically have very little null >>>> values >>> The very existence of ?. and ?[] suggests it is not quite the case — >>> otherwise, nobody would ever bother designing and implementing them. >>>> so am not convinced this is worth the effort (and as Jochen pointed >>>> out, there will still be cases where null will just be converted to >>>> "null"). >>> Are there? Given my limited knowledge, I know of none such. >>> “null?.plus('foo')” yields a null, and so — for a consistency >>> sake — very definitely should also “null?+'foo'” and >>> “@ImplicitSafeNavigation ... null+foo”, had they existed. >>>> What I would suggest instead is considering to introduce nil, >>>> sql_null, empty, ... as type agnostic constants in addition to the >>>> existing null in Groovy. That way you could use e.g. nil in your >>>> code, which by definition exhibits your expected behavior, but it >>>> would make the usage more explicit, and one would not need to >>>> switch/bend the existing null semantics... >>> That's a nice idea; alas, so that it is viable, one would also have to >>> be able to set up which kind of null is to be returned from >>> expressions like “aMap['unknownkey']“ or “list.find { >>> never-matches }” etc. >>> Thus, instead of my “@ImplicitSafeNavigation(true)” you would have >>> to use something like “@DefaultNullClass(nil)” — and instead of >>> “@ImplicitSafeNavigation(false)” you would need something like >>> “@DefaultNullClass(null)”. >>> Along with that, you would need a way to return “the current default >>> null” instead of just null; there would be a real problem with a >>> legacy code which returns null (but should return “the current >>> default null” instead), and so forth. >>> That all said, it definitely is an interesting idea worth checking; >>> myself, though, I do fear it would quickly lead to a real mess (unlike >>> my suggestion, which is considerably less flexible, but at the same >>> moment, very simple and highly intuitive). >>> Thanks and all the best, >>> OC >>>> -------- Ursprüngliche Nachricht -------- >>>> Von: "ocs@ocs" <o...@ocs.cz <mailto:o...@ocs.cz>> >>>> Datum: 15.08.18 00:53 (GMT+00:00) >>>> An: dev@groovy.apache.org <mailto:dev@groovy.apache.org> >>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation >>>> mg, >>>>> On 15 Aug 2018, at 1:33 AM, mg <mg...@arscreat.com >>>>> <mailto:mg...@arscreat.com>> wrote: >>>>> That's not how I meant my sample eval helper method to be used :-) >>>>> (for brevity I will write neval for eval(true) here) >>>>> What I meant was: How easy would it be to get a similar result to >>>>> what you want, by wrapping a few key places (e.g. a whole method >>>>> body) in your code in neval { ... } ? Evidently that would just >>>>> mean that any NPE inside the e.g. method would lead to the whole >>>>> method result being null. >>>> Which is a serious problem. Rarely you want „a whole method be >>>> skipped (and return null) if anything inside of it happens to be >>>> null“. What you normally want is the null-propagation, e.g., >>>> def foo=bar.baz[bax]?:default_value; >>>> ... other code ... >>>> The other code is _always_ performed and _never_ skipped (unless >>>> another exception occurs of course); but the null-propagation makes >>>> sure that if bar or bar.baz happens to be a null, then default_value >>>> is used. And so forth. >>>>> To give a simple example: >>>>> final x = a?.b?.c?.d >>>>> could be written as >>>>> final x = neval { a.b.c.d } >>>> Precisely. Do please note that even your simple example did not put >>>> a whole method body into neval, but just one sole expression >>>> instead. Essentially all expressions — often sub-expressions, >>>> wherever things like Elvis are used — would have to be embedded in >>>> nevals separately. Which is, alas, far from feasible. >>>>> Of course the two expressions are not semantically identical, >>>>> since neval will transform any NPE inside evaluation of a, b, c, >>>>> and d into the result null - but since you say you never want to >>>>> see any NPEs... >>>> That indeed would not be a problem. >>>>> (The performance of neval should be ok, since I do not assume that >>>>> you expect your code to actually encounter null values, and >>>>> accordingly NPEs, all the time) >>>> This one possibly would though: I _do_ expect my code to encounter >>>> null values often — with some code, they might well be the normal >>>> case with a non-null an exception. That's precisely why I do not >>>> want NPEs (but the quick, efficient and convenient null-propagation >>>> instead) :) >>>> Thanks and all the best, >>>> OC >>>> -------- Ursprüngliche Nachricht -------- >>>> Von: "ocs@ocs" <o...@ocs.cz <mailto:o...@ocs.cz>> >>>> Datum: 14.08.18 23:14 (GMT+00:00) >>>> An: dev@groovy.apache.org <mailto:dev@groovy.apache.org> >>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation >>>> mg, >>>> On 14 Aug 2018, at 11:36 PM, mg <mg...@arscreat.com >>>> <mailto:mg...@arscreat.com>> wrote: >>>> I am wondering: In what case does what you are using/suggesting >>>> differ significantly from simply catching a NPE that a specific code >>>> block throws and letting said block evaluate to null in that case: >>>> def eval(bool nullSafeQ, Closure cls) { >>>> try { >>>> return cls() >>>> } >>>> catch(NullPointerException e) { >>>> if(nullSafeQ) { >>>> return null >>>> } >>>> throw e >>>> } >>>> } >>>> Conceptually, not in the slightest. >>>> In practice, there's a world of difference. >>>> For one, it would be terrible far as the code cleanness, fragility >>>> and readability are concerned — even worse than those ubiquitous >>>> question marks: >>>> === the code should look, say, like this === >>>> @ImplicitSafeNavigation def foo(bar) { >>>> def x=baz(bar.foo)?:bax(bar.foo) >>>> x.allResults { >>>> def y=baz(it) >>>> if (y>1) y+bax(y-1) >>>> else y–bax(0) >>>> } >>>> } >>>> === the eval-based equivalent would probably look somewhat like this >>>> === >>>> def foo(bar) { >>>> def x=eval(true){baz(eval(true){bar.foo})?:bax(bar.foo)} >>>> eval(true){ >>>> x.allResults { >>>> def y=eval(true){baz(it)} >>>> if (y>1) eval(true){y+bax(y-1)} >>>> else eval(true){y–bax(0)} >>>> } >>>> } >>>> } >>>> === >>>> and quite frankly I am not even sure whether the usage of eval above >>>> is right and whether I did not forget to use it somewhere where it >>>> should have been. It would be _ways_ easier with those question >>>> marks. >>>> Also, with the eval block, there might be a bit of a problem with >>>> the type information: I regret to say I do not know whether we can >>>> in Groovy declare a method with a block argument in such a way that >>>> the return type of the function is automatically recognised by the >>>> compiler as the same type as the block return value? (Definitely I >>>> don't know how to do that myself; Cédric or Jochen might, though >>>> ;)) >>>> Aside of that, I wonder about the efficiency; although premature >>>> optimisation definitely is a bitch, still an exception harness is >>>> not cheap if an exception is caught, I understand. >>>> (It feels a bit like what you wants is tri-logic/SQL type NULL >>>> support in Groovy, not treating Java/Groovy null differently...) >>>> In fact what I want is a bit like the Objective-C simple but very >>>> efficient and extremely practical nil behaviour, to which I am used >>>> to and which suits me immensely. >>>> Agreed, the Java world takes a different approach (without even the >>>> safe navigation where it originated!); I have tried to embrace that >>>> approach a couple of times, and always I have found it seriously >>>> lacking. >>>> I do not argue that the null-propagating behaviour is always better; >>>> on the other hand, I do argue that sometimes and for some people it >>>> definitely is better, and that Groovy should support those times and >>>> people just as well as it supports the NPE-based approach of Java. >>>> Thanks and all the best, >>>> OC >>>> -------- Ursprüngliche Nachricht -------- >>>> Von: "ocs@ocs" <o...@ocs.cz <mailto:o...@ocs.cz>> >>>> Datum: 14.08.18 17:46 (GMT+00:00) >>>> An: dev@groovy.apache.org <mailto:dev@groovy.apache.org> >>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation >>>> Jochen, >>>> On 14 Aug 2018, at 6:25 PM, Jochen Theodorou <blackd...@gmx.org >>>> <mailto:blackd...@gmx.org>> >>>> wrote: >>>> Am 14.08.2018 um 15:23 schrieb ocs@ocs: >>>> H2, >>>> However, “a+b” should work as one would expect >>>> Absolutely. Me, I very definitely expect that if a happens to be >>>> null, the result is null too. (With b null it depends on the details >>>> of a.plus implementation.) >>> the counter example is null plus String though >>> Not for me. In my world, if I am adding a string to a non-existent >>> object, I very much do expect the result is still a non-existent >>> object. Precisely the same as if I has been trying to turn it to >>> lowercase or to count its character or anything. >>> Whilst I definitely do not suggest forcing this POV to others, to me, >>> it seems perfectly reasonable and 100 per cent intuitive. >>> Besides, it actually (and expectably) does work so, if I use the >>> method-syntax to be able to use safe navigation: >>> === >>> 254 /TMP> <q.groovy >>> String s=null >>> println "Should be null: ${s?.plus('foo')}" >>> 255 /TMP> /usr/local/groovy-2.4.15/bin/groovy q >>> WARNING: An illegal reflective access operation has occurred >>> ... ... >>> Should be null: null >>> 256 /TMP> >>> === >>> which is perfectly right. Similarly, a hypothetical “null?+'foo'” >>> or “@ImplicitSafeNavigation ... null+foo” should return null as >>> well, to keep consistent. >>> (Incidentally, do you — or anyone else — happen to know how to get >>> rid of those pesky warnings?) >>> Thanks and all the best, >>> OC