groovy-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
Subject Re: suggestion: ImplicitSafeNavigation annotation
Date Thu, 16 Aug 2018 07:32:30 GMT
See comment below.

Den 2018-08-15 14:55, skrev ocs@ocs:
> H2,
>> On 15 Aug 2018, at 9:47 AM, 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'
> }
> ===

This looks like a mix of effects and side effects.

The effect: The leftShift(object) method is supposed to return the 
TreeNode instance created by the createChildIfPossible(String name) 
method allowing subsequent calls as long as the return value is not 

The side effect: At the same time, createChildIfPossible(String name) 
and therefore also leftShift(object) is supposed to build a tree 
structure, mutating the internals of the TreeNode tn argument to the 
makeSubtreeIfPossible(TreeNode tn) method.

I like null propagation in evaluation chains which generate a some value 
to be assigned to a variable, but the same ting as a mechanism to stop 
creating side effects is not right at all in my book.

> 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 <> 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" <>
>> 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 <> 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" <>
>> 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 <> 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(
>> 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 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" <>
>> 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