groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From "ocs@ocs" <>
Subject Re: suggestion: ImplicitSafeNavigation annotation
Date Sun, 19 Aug 2018 00:20:44 GMT

if you think there's any commutative operator in Groovy, you are in for a very nasty surprise.

There's not, never has been, and — unless some future release decides to very seriously
break backward compatibility — never could be.

Consider e.g.,

741 /tmp> <q.groovy
class foo {
  def plus(o) { return "foo+$o" }
class bar {
  def plus(o) { return "bar+$o" }
def a=foo.newInstance(),b=bar.newInstance()
println "a+b=${a+b}"
println "b+a, on the other hand,=${b+a}"
742 /tmp> /usr/local/groovy-3.0.0-alpha-3/bin/groovy q
WARNING: Using incubator modules: jdk.incubator.httpclient
b+a, on the other hand,=bar+foo@4fcc0416
743 /tmp> 

All the best,

> On 18 Aug 2018, at 7:07 PM, mg <> wrote:
> That seems quite inconsistent to me, a bit like the a.equals(b) not implying b.equals(a)
inconsistency. I uphold that the order of execution of commutative operations should not influence
the result (think of automatic parallelization, where you could get an indeterministic bug
if the result depends on the order of execution).
> That Objective-C does not have a convention to at least try to guarantee commutative
method nil consistency is surprising (maybe you could supply an example why this does not
pose a problem in practice).
> I think we can agree we can safely ignore the case of someone explicitely throwing an
NPE in a method... ;-)
> @null.each { ... } : That would be my expectation also, but afaik null.each behavior
in dynamic Groovy has been around long before Elvis entered the building...
> Cheers,
> mg
> -------- Ursprüngliche Nachricht --------
> Von: "ocs@ocs" <>
> Datum: 16.08.18 04:39 (GMT+01:00)
> An:
> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
> mg,
>> On 15 Aug 2018, at 11:15 PM, mg < <>>
>> But in addition to this problem, you have the problem that for at least every commutative*
function/operator (plus, times, etc), you have to make sure that
>> notNullVariable.commutativeFunctionOrOperator(null)
>> must now return null, since
> If I really wanted to completely get rid of NPE, I would have to.
> Nevertheless,
> (a) it would be a vain effort; any called method could e.g., throw NPE explicitly if
it decides to. No remedy for that!
> (b) it is a normal way of things, just consider
> foo?.bar(baz)
> where baz happens to be null, foo does not, and does throw NPE upon a null
argument. “Safe navigaiton” not so safe in fact! :) It is ugly, but inevitable.
> Luckily, in practice, it is not a problem, for it does not happen often.
>> Since you cannot know if an arbitrary function is commutative, the function itself
would need to implement the correct behavior of returning null in that case. My impression
is that it seems exceedingly hard to bend a null-semantic language into a nil-direction, if
it has not been built that way to begin with.
> Actually this applies to ObjC either: although “nil.whatever(whichever_value)“ is
guaranteed by the runtime to be an empty nil-returning operation, “foo.whatever(bar)”
for a non-nil foo definitely can throw upon a nil bar (it would not be NPE, which does not
exist there, but it might be e.g., an InvalidArgumentException).
> No way to get rid of that — and no problem in practice either.
>> Apart from that I do not see the advantage you claim right now, and to me it is a
bug that in dynamic Groovy null.each { ... } simply does nothing (in static Groovy this throws
a NPE as expected), but I reserve judgement until I see your larger side by side example :-)
> I do not know the implementation and behaviour myself, but conceptually, I do believe
> - null.each { ... } should NPE
> - null?.each { ... } should silently do nothing
> quite regardless of static/dynamic. As always, I might be overlooking something of importance.
> All the best.,
> OC
>> -------- Ursprüngliche Nachricht --------
>> Von: "ocs@ocs" < <>>
>> Datum: 15.08.18 19:53 (GMT+00:00)
>> An: <>
>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>> Eric,
>>> On 15 Aug 2018, at 4:14 PM, Milles, Eric (TR Technology & Ops) <
<>> 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 < <>>
>>> Sent: Wednesday, August 15, 2018 7:55 AM
>>> To: <>
>>> Cc: <>
>>> Subject: Re: suggestion: ImplicitSafeNavigation annotation
>>> H2,
>>>> On 15 Aug 2018, at 9:47 AM, <>
>>>> 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 “”, 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 < <>>
>>>>>> 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
>>>>>> "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" < <>>
>>>>>> Datum: 15.08.18 00:53 (GMT+00:00)
>>>>>> An: <>
>>>>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>>>>>> mg,
>>>>>>> On 15 Aug 2018, at 1:33 AM, mg < <>>
>>>>>>> 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
>>>>>>> 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
>>>>>> 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,
>>>>>>> and d into the result null - but since you say you never want
>>>>>>> see any NPEs...
>>>>>> That indeed would not be a problem.
>>>>>>> (The performance of neval should be ok, since I do not assume
>>>>>>> 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" < <>>
>>>>>> Datum: 14.08.18 23:14 (GMT+00:00)
>>>>>> An: <>
>>>>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>>>>>> mg,
>>>>>> On 14 Aug 2018, at 11:36 PM, mg < <>>
>>>>>> 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(
>>>>>> 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){})?:bax(}
>>>>>> 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
>>>>>> 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
>>>>>> definitely is better, and that Groovy should support those times
>>>>>> 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" < <>>
>>>>>> Datum: 14.08.18 17:46 (GMT+00:00)
>>>>>> An: <>
>>>>>> Betreff: Re: suggestion: ImplicitSafeNavigation annotation
>>>>>> Jochen,
>>>>>> On 14 Aug 2018, at 6:25 PM, Jochen Theodorou <
>>>>>> 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 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

View raw message