I'd like to restart this discussion. I'm not sure if this should move to new
JIRA ticket or Pull Request. There are a few open questions I'd like to get
some feedback on. I have managed to solve the problem of getting from a null
array/iterable.
My use case is getting the first item from an iterable or else a supplied
default. Alternate proposals like "list?.first() ?: defaultValue" or
"list?.find() ?: defaultValue" do not properly handle all use cases. It took
quite a number of tries to figure it out.
"list ? list.first() : defaultValue" appears to be roughly equivalent.
However, having the additional DGMs would mean a one-time check for non-null,
non-empty and elimination of repeated "list", which may be a more complicated
expression. And in rare cases, a type could extend Iterable and provide a
non-standard implementation of asBoolean().
Proposed additions to DefaultGroovyMethods:
public static <T> T firstOrElse(Iterable<T> self, T defaultValue)
public static <T> T firstOrElse(List<T> self, T defaultValue) // allows use of
isEmpty() and get(i) instead of creating an Iterator instance
public static <T> T firstOrElse(T[] self, T defaultValue)
public static <T> T firstOrElse(org.codehaus.groovy.runtime.NullObject self, T
defaultValue) // exists solely for the (null).firstOrDefault(value) case
public static <T> T firstOrElse(Iterable<T> self, Supplier<T> defaultValue)
public static <T> T firstOrElse(List<T> self, Supplier<T> defaultValue)
public static <T> T firstOrElse(T[] self, Supplier<T> defaultValue)
public static <T> T firstOrElse(org.codehaus.groovy.runtime.NullObject self,
Supplier<T> defaultValue)
Since this is targeted at Groovy 2.5+ I have selected
java.util.function.Supplier instead of groovy.lang.Closure. Although that
could be changed if a new Groovy 2.4 minor release was planned.
Usage:
Iterable<Object> iterable = null
println iterable.firstOrElse('default') // prints 'default'
iterable = []
println iterable.firstOrElse('default') // prints 'default'
iterable = [0]
println iterable.firstOrElse('default') // prints 0
iterable = [null]
println iterable.firstOrElse('default') // prints null
iterable = [false]
println iterable.firstOrElse('default') // prints false
iterable = null
iterable.firstOrElse { -> throw new ExceptionOfMyChoosing() } // throws
Open items:
1. Should a set of methods "lastOrElse" also be created? It would not take
much effort to add them at the same time.
2. Should the "getAt" methods be similarly extended, like "public static <T>
T getAt(List<T> self, int idx, T defaultValue)", etc.
3. If "getAt" is extended in this way, would it be useful to also consider
extending the "[idx]" syntax form of "getAt" to include the default?
4. There was a question raised: if "iterable" contains Closures or
Suppliers, how should that case be handled? I'm curious how often this might
come up.
________________________________
From: Milles, Eric (TR Technology & Ops) <[email protected]>
Sent: Friday, October 19, 2018 10:54 AM
To: [email protected]
Subject: Re: DGM for first or default
These may seem a bit simple for DGMs, but they do solve a couple problems as
compared to "list ? list.first() ?: defaultValue". First, no repetition of the
"list" expression, which may be complex. Second, content assist will show
proposals for these methods. Third, the Groovy truth problems with "list?[0]
?: defaultValue", "list?.getAt(0) ?: defaultValue", "list.find() ?:
defaultValue" and others do not exist for these. If the first element is null
or otherwise falsy, it will be returned as desired. The only case for me that
is unresolved is a null array or iterable. In this case, Groovy throws "Cannot
invoke method firstOrElse() on null obect" instead of running the method
allowing null tolerance and return of default value.
public static <T> T firstOrElse(Iterable<T> self, T defaultValue) {
Iterator<T> iter = self.iterator();
if (iter.hasNext()) {
return iter.next();
}
return defaultValue;
}
public static <T> T firstOrElse(T[] self, T defaultValue) {
if (self.length > 0) {
return self[0];
}
return defaultValue;
}
// and similarly for the Closure (or java.util.function.Supplier if Java 8+
only) variants
Since safe navigation is being explored for by-index access, is there a
possibility for including some for of "or supplied default" in any of the
safe-navigation cases? I personally find the new safe-indexing syntax to be
unnecessary when "list?.getAt(i)" appears to be the equivalent to "list?[i]".
Alternate proposal, what if the DGM.getAt(self, int idx) had variants that
included a default value return instead of hard-coded null? Like this:
public static <T> T getAt(List<T> self, int idx, T def) {
int size = self.size();
int i = normaliseIndex(idx, size);
if (i < size) {
return self.get(i);
} else {
//return null;
return def;
}
}
________________________________
From: Mario Garcia <[email protected]>
Sent: Thursday, October 18, 2018 6:19 PM
To: [email protected]
Subject: Re: DGM for first or default
Good point OC:
[0,'',[],[:]].find()?:'not quite what you wanted here'
[0,1,2].find()?:'nor in this case'
The more I think on this the more I think is an interesting topic. I fully
understand your frustration with first(), but apart from the example with Cocoa
you mentioned, looking in the JVM it turns out there're plenty of examples of
language collections behaving that way:
In scala the head of an empty list does throw an exception
-------------------
scala> var empty = List[Int]()
empty: List[Int] = List()
scala> empty.head
java.util.NoSuchElementException: head of empty list
at scala.collection.immutable.Nil$.head(List.scala:426)
at scala.collection.immutable.Nil$.head(List.scala:423)
... 28 elided
scala>
---------------------
and so does kotlin when calling to first()
----------------------
Welcome to Kotlin version 1.2.71 (JRE 1.8.0_171-b11)
Type :help for help, :quit for quit
>>> val num: List<Int> = listOf()
>>> num.first()
java.util.NoSuchElementException: List is empty.
at kotlin.collections.CollectionsKt___CollectionsKt.first(_Collections.kt:184)
>>>
---------------------
in Kotlin they have firstOrNull(), but I haven't found any overloaded function
with a default value. They also have "find", but it's not possible to call it
without parameter
However Clojure returns null whether:
* The first element was nil
* The list was empty
* Or the list was nil
--------------------
user=> (def a nil)
#'user/a
user=> a
nil
user=> (first a)
nil
user=> (def a '(nil))
#'user/a
user=> a
(nil)
user=> (first a)
nil
user=> (def a '())
#'user/a
user=> a
()
user=> (first a)
nil
user=>
-------------------
BTW I forgot to mention that Groovy 3 will have safe indexing meaning an
expression like the following:
* will return the first element of a non empty list which I guess it will
be the Kotlin firstOrNull() equivalent
* or null if the list was null or empty
---------
// trying to get first element from null list
nullList?[0] ==> null
// trying to get an inexistent element from a non empty list (but this is not
new, this is how a non empty list indexing works in Groovy)
nonNullList?[9999] => null
----------
Outside the JVM, Haskell, when asking for the head of an empty list, throws an
exception (There is an explanation in stackoverflow which I'm afraid I don't
understand). So in the end Groovy's first() seems not to be the exception among
other modern languages out there.
Another point of view, could be thinking about returning null consistently.
Lets say a list returns null using first():
* Does it mean the first element is a null value or is an empty list and
that's why is giving me a null value ?
* What if null is a valid value, with some meaning in my process ? With
that context a method like firstOrNull() (even first(defaultValue) with a null
list) could be considered ambiguous.
My guess is that in the case of languages throwing an exception using first()
on an empty list, when they designed the language collections they didn't have
any other way to express that kind of semantics. But this is just a lucky
guess. I'm probably wrong. I only can think of pattern matching as a complete
solution, where the terminal result in the case of an empty or null list, is a
default value different than any of the elements of the expected result set ?
I apologize in advance for the lengthy e-mail, but it seemed interesting to
think why first() was designed like that, not only in Groovy, but in some other
languages as well.
Mario
El jue., 18 oct. 2018 a las 20:27, Milles, Eric (TR Technology & Ops)
(<[email protected]<mailto:[email protected]>>)
escribió:
I think first() exists so there is a semantic pair for functional programming:
first()/head() and tail() or init() and last()
________________________________
From: ocs@ocs <[email protected]<mailto:[email protected]>>
Sent: Thursday, October 18, 2018 1:20 PM
To: [email protected]<mailto:[email protected]>
Subject: Re: DGM for first or default
Well I thought first is smart enough to return null for an empty list, same as
my firstObject in Cocoa does. If it throws, what's on earth point of having the
thing at all? In that case it can be replaced by list[0] without any drawback
at all.
All the best,
OC
On 18 Oct 2018, at 7:19 PM, Milles, Eric (TR Technology & Ops)
<[email protected]<mailto:[email protected]>> wrote:
"list?.first() ?: defaultValue" is not the equivalent. If the collection is
empty, first() throws an IndexOutOfBoundsException is thrown. That's why I'm
asking if there is a simple equivalent. I suppose this is the equivalent now
that I think about it:
list ? list.first() : defaultValue
________________________________
From: ocs@ocs <[email protected]<mailto:[email protected]>>
Sent: Thursday, October 18, 2018 12:07 PM
To: [email protected]<mailto:[email protected]>
Subject: Re: DGM for first or default
Myself, I am not a huge fan of adding not-often-needed functionalities (and
actually would add almost none of those discussed lately); nevertheless...
On 18 Oct 2018, at 6:48 PM, Paolo Di Tommaso
<[email protected]<mailto:[email protected]>> wrote:
-1, it can be easily done as:
list.first() ?: defaultValue
... this won't work in case the first object is a Groovy False (e.g., an empty
string, or a plethora of others).