mg,
> On 15 Aug 2018, at 3:26 AM, mg <[email protected]> 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" <[email protected]>
> Datum: 15.08.18 00:53 (GMT+00:00)
> An: [email protected]
> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>
> mg,
>
>> On 15 Aug 2018, at 1:33 AM, mg <[email protected]
>> <mailto:[email protected]>> 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" <[email protected] <mailto:[email protected]>>
>> Datum: 14.08.18 23:14 (GMT+00:00)
>> An: [email protected] <mailto:[email protected]>
>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>>
>> mg,
>>
>>> On 14 Aug 2018, at 11:36 PM, mg <[email protected]
>>> <mailto:[email protected]>> 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" <[email protected] <mailto:[email protected]>>
>>> Datum: 14.08.18 17:46 (GMT+00:00)
>>> An: [email protected] <mailto:[email protected]>
>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>>>
>>> Jochen,
>>>
>>>> On 14 Aug 2018, at 6:25 PM, Jochen Theodorou <[email protected]
>>>> <mailto:[email protected]>> 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
>>>
>>>
>>>
>>
>