From a50db9ad675a45ffe8ec811b3e64a61f43427469 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Tue, 12 Dec 2017 23:23:39 +0000 Subject: [PATCH] Add Around combinator --- .../net/kemitix/mon/combinator/Around.java | 101 ++++++++++++++++++ .../net/kemitix/mon/combinator/AfterTest.java | 1 - .../kemitix/mon/combinator/AroundTest.java | 46 ++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/kemitix/mon/combinator/Around.java create mode 100644 src/test/java/net/kemitix/mon/combinator/AroundTest.java diff --git a/src/main/java/net/kemitix/mon/combinator/Around.java b/src/main/java/net/kemitix/mon/combinator/Around.java new file mode 100644 index 0000000..b6d8e2e --- /dev/null +++ b/src/main/java/net/kemitix/mon/combinator/Around.java @@ -0,0 +1,101 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 Paul Campbell + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE + * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package net.kemitix.mon.combinator; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * Around pattern combinator. + * + *

Original from http://boundsofjava.com/newsletter/003-introducing-combinators-part1

+ * + * @param the argument type + * @param the result type + * + * @author Federico Peralta Schaffner (fps@boundsofjava.com) + */ +@FunctionalInterface +public interface Around extends + Function< + Function, + Function< + BiConsumer, T>, + Function>> { + + /** + * Decorates a function with an BiConsumer that will be supplier with an executable to perform the function, and the + * argument that will be applied when executed. + * + * @param function the function to apply the argument to and return the result value of + * @param around the bi-consumer that will supplied with the executable and the argument + * @param the argument type + * @param the result type + * + * @return a partially applied Function that will take an argument, and the result of applying it to function + */ + static Function decorate( + final Function function, + final BiConsumer, T> around + ) { + return Around.create().apply(function) + .apply(around); + } + + /** + * Create an Around curried function. + * + * @param the argument type + * @param the result type + * + * @return a curried function that will execute the around function, passing an executable and the invocations + * argument. The around function must {@code execute()} the executable and may capture the result. + */ + static Around create() { + return function -> around -> argument -> { + final AtomicReference result = new AtomicReference<>(); + final Executable callback = () -> { + result.set(function.apply(argument)); + return result.get(); + }; + around.accept(callback, argument); + return result.get(); + }; + } + + /** + * The executable that will be supplied to the around function to trigger the surrounded function. + * + * @param the return type of the function + */ + @FunctionalInterface + interface Executable { + + /** + * Executes the function. + * + * @return the result of applying the function + */ + R execute(); + } +} diff --git a/src/test/java/net/kemitix/mon/combinator/AfterTest.java b/src/test/java/net/kemitix/mon/combinator/AfterTest.java index cf82ccd..e8efe77 100644 --- a/src/test/java/net/kemitix/mon/combinator/AfterTest.java +++ b/src/test/java/net/kemitix/mon/combinator/AfterTest.java @@ -16,7 +16,6 @@ public class AfterTest { final List events = new ArrayList<>(); final Function squareDecorated = After.decorate(i -> function(i, events), (v, r) -> after(v, r, events)); - //when final Integer result = squareDecorated.apply(2); //then diff --git a/src/test/java/net/kemitix/mon/combinator/AroundTest.java b/src/test/java/net/kemitix/mon/combinator/AroundTest.java new file mode 100644 index 0000000..539525e --- /dev/null +++ b/src/test/java/net/kemitix/mon/combinator/AroundTest.java @@ -0,0 +1,46 @@ +package net.kemitix.mon.combinator; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AroundTest { + + @Test + public void canCreateAnAroundCombinator() { + //given + final List events = new ArrayList<>(); + final Function squareDecorated = + Around.decorate( + i -> function(i, events), + (executable, argument) -> around(executable, argument, events) + ); + //when + final Integer result = squareDecorated.apply(2); + //then + assertThat(result).isEqualTo(4); + assertThat(events).containsExactly("around before 2", "function", "around after 4"); + } + + private void around( + final Around.Executable executable, + final Integer argument, + final List events + ) { + events.add("around before " + argument); + final Integer result = executable.execute(); + events.add("around after " + result); + } + + private static Integer function( + final Integer argument, + final List events + ) { + events.add("function"); + return argument * argument; + } +}