Identity of Lambdas (was Re: JEP 186: Collection Literals)

Remi Forax forax at univ-mlv.fr
Sat Jan 18 09:57:27 PST 2014


On 01/18/2014 06:13 PM, Peter Levart wrote:
>
> On 01/18/2014 02:49 PM, Remi Forax wrote:
>> On 01/18/2014 12:54 PM, Millies, Sebastian wrote:
>>> Surprising, at least when one thinks of lambdas as functions, not objects.
>>>
>>> I was under the impression that lambdas could not be used as hash keys, because they had no
>>> well-defined identity. The source for this belief was Stuart Mark's answer at
>>> http://stackoverflow.com/questions/15221659/java-8-lambda-expression-and-first-class-values
>>> from which I quote:
>>>
>>> <quote>
>>> Given that lambdas are converted into objects, they inherit (literally) all the characteristics of objects. In particular, objects:
>>>
>>>       have various methods like equals, getClass, hashCode, notify, toString, and wait
>>>       have an identity hash code
>>>       can be locked by a synchronized block
>>>       can be compared using the == and != and instanceof operators
>>>
>>> and so forth. In fact, all of these are irrelevant to the intended usage of lambdas. Their behavior is essentially undefined. You can write a program that uses any of these, and you will get some result, but the result may differ from release to release (or even run to run!).
>>> </quote>
>>>
>>> In Peter's example, the only thing that seems to matter would be that if a lambda has an identity hash code, that will not change while the JVM exists. But will the antecedent always be true in the future (function types etc.?)
>>> Are there some minimal guarantees on the identities of lambdas, and where are they described?
>>>
>>> I took Stuart's remark "Their behavior is essentially undefined" to mean: Don't do these things with lambdas! Would that be sound advice?
>>>
>>> -- Sebastian
>> Lambdas have not identity so by example,
>>     IntBinaryOperation fun = x -> x;
>>     IntBinaryOperation fun2 = fun;
>>     fun == fun2
>> may be true then false if executed several times.
>
> By "several times", you mean each time with next major release of Java ?
>
> Peter

No, several times inside the same program.
The lambda identity may not survive the opt/deopt dance.

The JIT can decide to create no object and if the == occurs in a branch 
that was never taken,
the reference to the original object may disappear but if later the 
branch is executed, a lambda object need to be reconstructed, but it 
will have another identity.

Rémi

>
>> So minimal guarantees are that there is no guarantee :)
>>
>> Rémi
>>> -----Original Message-----
>>> From:lambda-dev-bounces at openjdk.java.net  [mailto:lambda-dev-bounces at openjdk.java.net] On Behalf Of Peter Levart
>>> Sent: Saturday, January 18, 2014 12:17 PM
>>> To: Remi Forax;lambda-dev at openjdk.java.net
>>> Subject: [Spam]: Re: JEP 186: Collection Literals
>>>
>>>
>>> On 01/16/2014 12:43 AM, Remi Forax wrote:
>>>> We can also use the Builder Pattern of Ruby now that we have a lambda syntax
>>>>       new ArrayList<>(builder -> builder.add(1).add(2).add(3));
>>>>
>>>> Rémi
>>> This one is interesting, because it shows what can be achieved with lambdas today. It enables expressions that evaluate into singleton objects (like non-capturing lambdas). If the lambda body is non-capturing, it evaluates into constant singleton in current implementation. So objects produced with such lambdas can be cached (using lambda object as a weak key). Here's a sample test that exercises
>>> that:
>>>
>>> public class Test {
>>>        public static void main(String[] args) {
>>>            for (int i = 0; i < 3; i++) {
>>>                List<Double> list = immutableList(b -> b._(3.14)._(0.33333));
>>>                System.out.println(list + " : " + System.identityHashCode(list));
>>>            }
>>>            System.out.println();
>>>            for (int i = 0; i < 3; i++) {
>>>                double x = i;
>>>                List<Double> list = immutableList(b -> b._(3.14)._(0.33333)._(x));
>>>                System.out.println(list + " : " + System.identityHashCode(list));
>>>            }
>>>            System.out.println();
>>>            for (int i = 0; i < 3; i++) {
>>>                Map<String, Integer> map = immutableMap(b -> b._("aa", 1)._("bb", 2)._("cc", 3));
>>>                System.out.println(map + " : " + System.identityHashCode(map));
>>>            }
>>>            System.out.println();
>>>            for (int i = 0; i < 3; i++) {
>>>                int x = i;
>>>                Map<String, Integer> map = immutableMap(b -> b._("aa", 1)._("bb", 2)._("cc", x));
>>>                System.out.println(map + " : " + System.identityHashCode(map));
>>>            }
>>>        }
>>> }
>>>
>>> which prints:
>>>
>>> [3.14, 0.33333] : 1706377736
>>> [3.14, 0.33333] : 1706377736
>>> [3.14, 0.33333] : 1706377736
>>>
>>> [3.14, 0.33333, 0.0] : 868693306
>>> [3.14, 0.33333, 1.0] : 989110044
>>> [3.14, 0.33333, 2.0] : 321001045
>>>
>>> {aa=1, bb=2, cc=3} : 1044036744
>>> {aa=1, bb=2, cc=3} : 1044036744
>>> {aa=1, bb=2, cc=3} : 1044036744
>>>
>>> {aa=1, bb=2, cc=0} : 1915318863
>>> {aa=1, bb=2, cc=1} : 295530567
>>> {aa=1, bb=2, cc=2} : 1324119927
>>>
>>>
>>>
>>> Regards, Peter
>>>
>>>
>>> P.S. Here's what I used to compile above test:
>>>
>>> public interface Builder<T> extends Consumer<T> {
>>>        default Builder<T> _(T t) {
>>>            accept(t);
>>>            return this;
>>>        }
>>> }
>>>
>>> public interface BiBuilder<T, U> extends BiConsumer<T, U> {
>>>        default BiBuilder<T, U> _(T t, U u) {
>>>            accept(t, u);
>>>            return this;
>>>        }
>>> }
>>>
>>> public class Collections2 {
>>>
>>>        public static <T> ArrayList<T> arrayList(Consumer<Builder<T>>
>>> producer) {
>>>            ArrayList<T> list = new ArrayList<>();
>>>            producer.accept(list::add);
>>>            return list;
>>>        }
>>>
>>>        // cache of immutable lists per producer
>>>        private static final Map<Consumer<? extends Builder<?>>, List<?>> INTERNED_IMMUTABLE_LISTS = new WeakHashMap<>();
>>>
>>>        public static <T> List<T> immutableList(Consumer<Builder<T>>
>>> producer) {
>>>            synchronized (INTERNED_IMMUTABLE_LISTS) {
>>>                // check if already interned
>>>                @SuppressWarnings("unchecked")
>>>                List<T> list = (List<T>)
>>> INTERNED_IMMUTABLE_LISTS.get(producer);
>>>                if (list != null) return list;
>>>            }
>>>
>>>            // count elements produced
>>>            final int[] count = new int[1];
>>>            producer.accept(t -> count[0]++);
>>>
>>>            // construct new list
>>>            ArrayList<T> aList = new ArrayList<>(count[0]);
>>>            producer.accept(aList::add);
>>>
>>>            synchronized (INTERNED_IMMUTABLE_LISTS) {
>>>                // recheck (highly unlikely)
>>>                @SuppressWarnings("unchecked")
>>>                List<T> list = (List<T>)
>>> INTERNED_IMMUTABLE_LISTS.get(producer);
>>>                if (list != null) return list;
>>>                // put it into cache and return
>>>                list = Collections.unmodifiableList(aList);
>>>                INTERNED_IMMUTABLE_LISTS.put(producer, list);
>>>                return list;
>>>            }
>>>        }
>>>
>>>        public static <K, V> HashMap<K, V> hashMap(Consumer<BiBuilder<K,
>>> V>> producer) {
>>>            HashMap<K, V> map = new HashMap<>();
>>>            producer.accept(map::put);
>>>            return map;
>>>        }
>>>
>>>        // cache of immutable maps per producer
>>>        private static final Map<Consumer<? extends BiBuilder<?, ?>>, Map<?, ?>> INTERNED_IMMUTABLE_MAPS = new WeakHashMap<>();
>>>
>>>        public static <K, V> Map<K, V> immutableMap(Consumer<BiBuilder<K,
>>> V>> producer) {
>>>            synchronized (INTERNED_IMMUTABLE_MAPS) {
>>>                // check if already interned
>>>                @SuppressWarnings("unchecked")
>>>                Map<K, V> map = (Map<K, V>) INTERNED_IMMUTABLE_MAPS.get(producer);
>>>                if (map != null) return map;
>>>            }
>>>
>>>            // count elements produced
>>>            final int[] count = new int[1];
>>>            producer.accept((k, v) -> count[0]++);
>>>
>>>            // construct new map
>>>            HashMap<K, V> hMap = new HashMap<>(count[0] * 4 / 3);
>>>            producer.accept(hMap::put);
>>>
>>>            synchronized (INTERNED_IMMUTABLE_MAPS) {
>>>                // recheck (highly unlikely)
>>>                @SuppressWarnings("unchecked")
>>>                Map<K, V> map = (Map<K, V>) INTERNED_IMMUTABLE_MAPS.get(producer);
>>>                if (map != null) return map;
>>>                // put it into cache and return
>>>                map = Collections.unmodifiableMap(hMap);
>>>                INTERNED_IMMUTABLE_MAPS.put(producer, map);
>>>                return map;
>>>            }
>>>        }
>>> }
>>>
>>>
>>>
>>>
>>> Software AG – Sitz/Registered office: Uhlandstraße 12, 64297 Darmstadt, Germany – Registergericht/Commercial register: Darmstadt HRB 1562 - Vorstand/Management Board: Karl-Heinz Streibich (Vorsitzender/Chairman), Dr. Wolfram Jost, Arnd Zinnhardt; - Aufsichtsratsvorsitzender/Chairman of the Supervisory Board: Dr. Andreas Bereczky -http://www.softwareag.com
>>>
>>>
>



More information about the lambda-dev mailing list