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

Reply via email to