TAP5-2658: tapestry-func Tuple custom hashCode on values

diff --git a/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java b/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java
index 40759a7..eaddb18 100644
--- a/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java
+++ b/tapestry-func/src/main/java/org/apache/tapestry5/func/Tuple.java
@@ -14,6 +14,8 @@
 
 package org.apache.tapestry5.func;
 
+import java.util.Objects;
+
 /**
  * A Tuple holds two values of two different types.
  * 
@@ -89,6 +91,18 @@
     }
 
     /**
+     * Returns a hash code value for the tuple, based on its values.
+     *
+     * @return a hash code value for this tuple.
+     * @since 5.7
+     */
+    @Override
+    public int hashCode()
+    {
+        return Objects.hash(this.first, this.second);
+    }
+
+    /**
      * The heart of {@link #equals(Object)}; the other object is the same class as this object.
      * 
      * @param other
diff --git a/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java b/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java
index 283387b..3eadf37 100644
--- a/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java
+++ b/tapestry-func/src/test/java/org/apache/tapestry5/func/TupleTests.java
@@ -55,4 +55,16 @@
     {
         assertTrue(t.equals(Tuple.create("tapestry", 5)));
     }
+
+    @Test
+    public void equivalent_tuples_have_equal_hashcode()
+    {
+        assertTrue(t.hashCode() == Tuple.create("tapestry", 5).hashCode());
+    }
+
+    @Test
+    public void unequal_values_have_different_hashcode()
+    {
+        assertFalse(t.hashCode() == Tuple.create("tapestry", 4).hashCode());
+    }
 }