diff --git a/actor-tests/src/test/java/org/apache/pekko/japi/FunctionLawTest.java b/actor-tests/src/test/java/org/apache/pekko/japi/FunctionLawTest.java new file mode 100644 index 0000000000..63f92ad6f0 --- /dev/null +++ b/actor-tests/src/test/java/org/apache/pekko/japi/FunctionLawTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pekko.japi; + +import org.apache.pekko.japi.function.Function; +import org.apache.pekko.japi.function.Function2; +import org.apache.pekko.japi.function.Predicate; +import org.apache.pekko.japi.function.Predicate2; +import org.junit.Assert; +import org.junit.Test; + +public class FunctionLawTest { + + @Test + public void testFunctionCompose() throws Exception { + Function f = v1 -> v1 + 1; + Function g = v1 -> v1 * 2; + Function h = f.compose(g); + Assert.assertEquals(5L, h.apply(2L).longValue()); + Assert.assertEquals(6L, g.compose(f).apply(2L).longValue()); + } + + @Test + public void testFunctionAndThen() throws Exception { + Function f = v1 -> v1 + 1; + Function g = v1 -> v1 * 2; + Function h = f.andThen(g); + Assert.assertEquals(6L, h.apply(2L).longValue()); + Assert.assertEquals(5L, g.andThen(f).apply(2L).longValue()); + } + + @Test + public void testFunction2AndThen() throws Exception { + Function2 f = Long::sum; + Function g = v1 -> v1 * 2; + Function2 h = f.andThen(g); + Assert.assertEquals(10L, h.apply(2L, 3L).longValue()); + } + + @Test + public void testPredicateNegate() { + final Predicate alwaysTrue = o -> true; + final Predicate alwaysFalse = alwaysTrue.negate(); + Assert.assertTrue(alwaysTrue.test(new Object())); + Assert.assertFalse(alwaysFalse.test(new Object())); + Assert.assertTrue(alwaysTrue.negate().negate().test(new Object())); + } + + @Test + public void testPredicate2Negate() { + final Predicate2 alwaysTrue = (o1, o2) -> true; + final Predicate2 alwaysFalse = alwaysTrue.negate(); + Assert.assertTrue(alwaysTrue.test(new Object(), new Object())); + Assert.assertFalse(alwaysFalse.test(new Object(), new Object())); + Assert.assertTrue(alwaysTrue.negate().negate().test(new Object(), new Object())); + } +} diff --git a/actor/src/main/scala/org/apache/pekko/japi/function/Function.scala b/actor/src/main/scala/org/apache/pekko/japi/function/Function.scala index 250c20dc20..669e634800 100644 --- a/actor/src/main/scala/org/apache/pekko/japi/function/Function.scala +++ b/actor/src/main/scala/org/apache/pekko/japi/function/Function.scala @@ -28,6 +28,18 @@ import scala.annotation.nowarn trait Function[-T, +R] extends java.io.Serializable { @throws(classOf[Exception]) def apply(param: T): R + + /** + * That is, the resulting function is equivalent to `this(g(v))`. + * @since 2.0.0 + */ + def compose[V](g: Function[V, T]): Function[V, R] = (v: V) => this.apply(g.apply(v)) + + /** + * Creates a composed function that first applies this function to its input, then applies `g` to the result. + * @since 2.0.0 + */ + def andThen[V](g: Function[R, V]): Function[T, V] = (t: T) => g.apply(this.apply(t)) } object Function { @@ -50,6 +62,16 @@ object Function { trait Function2[-T1, -T2, +R] extends java.io.Serializable { @throws(classOf[Exception]) def apply(arg1: T1, arg2: T2): R + + /** + * Compose this function with another function `g`, such that the resulting function + * is equivalent to `g(this(x1, x2))`. + * This creates a composed function that first applies this function to its arguments, + * and then applies function `g` to the result. + * @since 2.0.0 + */ + def andThen[V](g: Function[R, V]): Function2[T1, T2, V] = + (t1: T1, t2: T2) => g.apply(this.apply(t1, t2)) } /** @@ -89,6 +111,12 @@ trait Effect extends java.io.Serializable { @FunctionalInterface trait Predicate[-T] extends java.io.Serializable { def test(param: T): Boolean + + /** + * Returns a predicate that represents the logical negation of this predicate. + * @since 2.0.0 + */ + def negate: Predicate[T] = (t: T) => !this.test(t) } /** @@ -101,6 +129,12 @@ trait Predicate[-T] extends java.io.Serializable { @FunctionalInterface trait Predicate2[-T1, -T2] extends java.io.Serializable { def test(param1: T1, param2: T2): Boolean + + /** + * Returns a predicate that represents the logical negation of this predicate. + * @since 2.0.0 + */ + def negate: Predicate2[T1, T2] = (t1: T1, t2: T2) => !this.test(t1, t2) } /**