Merge pull request #49 from kemitix/lazy

[lazy] Add a lazy evaluator
This commit is contained in:
Paul Campbell 2018-09-30 19:43:16 +01:00 committed by GitHub
commit 58542352d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 358 additions and 0 deletions

View file

@ -25,8 +25,10 @@
- [Maybe] - [Maybe]
- [Result] - [Result]
- [Tree] - [Tree]
- [Lazy]
- [Either] - [Either]
** Maven Usage ** Maven Usage
#+BEGIN_SRC xml #+BEGIN_SRC xml
@ -925,6 +927,51 @@
#+END_SRC #+END_SRC
** Lazy
A Lazy evaluated expression. Using a Supplier to provide the value, only
evaluates the value when required, and never more than once.
*** Static Constructors
**** =static <R> Lazy<R> of(Supplier<R> supplier)=
Create a new Lazy value from the supplier.
#+BEGIN_SRC java
final Suppler<UUID> supplier = ...;
final Lazy<UUID> lazy = Lazy.of(supplier);
#+END_SRC
*** Instance Methods
**** =boolean isEvaluated()=
Checks if the value has been evaluated.
#+BEGIN_SRC java
final Lazy<UUID> lazy = ...;
final boolean isEvaluated = lazy.isEvaluated();
#+END_SRC
**** =T value()=
The value, evaluating it if necessary.
#+BEGIN_SRC java
final Lazy<UUID> lazy = ...;
final UUID value = lazy.value();
#+END_SRC
**** =<R> Lazy<R> map(Function<T, R> f)=
Maps the Lazy instance into a new Lazy instance using the function.
#+BEGIN_SRC java
final Lazy<UUID> uuidLazy = ...;
final Lazy<String> stringLazy = uuidLazy.map(v -> v.toString());
#+END_SRC
** Either ** Either
Allows handling a value that can be one of two types, a left value/type or a Allows handling a value that can be one of two types, a left value/type or a

View file

@ -0,0 +1,67 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 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.lazy;
import net.kemitix.mon.Functor;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Lazy evaluation of 'expensive' expressions.
*
* @param <T> the type of the value
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
public interface Lazy<T> extends Functor<T, Lazy<?>> {
/**
* Create a new Lazy value from the supplier.
*
* @param supplier the source of the value
* @param <R> the type of the value
* @return a Lazy wrapper of the Supplier
*/
public static <R> Lazy<R> of(final Supplier<R> supplier) {
return new LazySupplier<>(supplier);
}
/**
* Checks if the value has been evaluated.
*
* @return true if the value has been evaluated.
*/
public abstract boolean isEvaluated();
/**
* The value, evaluating it if necessary.
*
* <p>Does not evaluate the value more than once.</p>
*
* @return the evaluated value
*/
public abstract T value();
@Override
public abstract <R> Lazy<R> map(Function<T, R> f);
}

View file

@ -0,0 +1,75 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 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.lazy;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Implementation of Lazy using a Supplier.
*
* @param <T> the type of the value
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
class LazySupplier<T> implements Lazy<T> {
private final Supplier<T> supplier;
private final AtomicBoolean evaluated = new AtomicBoolean(false);
private final AtomicReference<T> value = new AtomicReference<>();
/**
* Creates a new Lazy wrapper for the Supplier.
*
* @param supplier the source of the value
*/
LazySupplier(final Supplier<T> supplier) {
this.supplier = supplier;
}
@Override
public boolean isEvaluated() {
return evaluated.get();
}
@Override
public T value() {
if (evaluated.get()) {
return value.get();
}
synchronized (value) {
if (!evaluated.get()) {
value.set(supplier.get());
evaluated.set(true);
}
}
return value.get();
}
@Override
public <R> Lazy<R> map(final Function<T, R> f) {
return Lazy.of(() -> f.apply(value()));
}
}

View file

@ -0,0 +1,28 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2018 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.
*/
/**
* Deferred evaluation.
*
* @author Paul Campbell (pcampbell@kemitix.net)
*/
package net.kemitix.mon.lazy;

View file

@ -0,0 +1,141 @@
package net.kemitix.mon.lazy;
import org.assertj.core.api.WithAssertions;
import org.junit.Test;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public class LazySupplierTest implements WithAssertions {
@Test
public void whenCreateLazyThenSupplierIsNotCalled() {
//given
final AtomicBoolean supplierCalled = new AtomicBoolean(false);
final Supplier<UUID> supplier = () -> {
supplierCalled.set(true);
return UUID.randomUUID();
};
//when
final Lazy<UUID> lazy = Lazy.of(supplier);
//then
assertThat(supplierCalled).isFalse();
}
@Test
public void whenCreateLazyThenIsEvaluatedIsFalse() {
//given
final Supplier<UUID> supplier = UUID::randomUUID;
//when
final Lazy<UUID> lazy = Lazy.of(supplier);
//then
assertThat(lazy.isEvaluated()).isFalse();
}
@Test
public void whenValueThenSupplierIsCalled() {
//given
final AtomicBoolean supplierCalled = new AtomicBoolean(false);
final Supplier<UUID> supplier = () -> {
supplierCalled.set(true);
return UUID.randomUUID();
};
final Lazy<UUID> lazy = Lazy.of(supplier);
//when
lazy.value();
//then
assertThat(supplierCalled).isTrue();
}
@Test
public void whenValueThenValueIsSameAsSupplier() {
//given
final UUID uuid = UUID.randomUUID();
final Supplier<UUID> supplier = () -> uuid;
final Lazy<UUID> lazy = Lazy.of(supplier);
//when
final UUID value = lazy.value();
//then
assertThat(value).isSameAs(uuid);
}
@Test
public void whenValueThenIsEvaluatedIsTrue() {
//given
final Supplier<UUID> supplier = () -> UUID.randomUUID();
final Lazy<UUID> lazy = Lazy.of(supplier);
//when
lazy.value();
//then
assertThat(lazy.isEvaluated()).isTrue();
}
@Test
public void whenValueCalledTwiceThenSupplierIsNotCalledAgain() {
//given
final AtomicInteger supplierCalledCounter = new AtomicInteger(0);
final Supplier<UUID> supplier = () -> {
supplierCalledCounter.incrementAndGet();
return UUID.randomUUID();
};
final Lazy<UUID> lazy = Lazy.of(supplier);
//when
lazy.value();
lazy.value();
//then
assertThat(supplierCalledCounter).hasValue(1);
}
@Test
public void whenMapLazyThenSupplierNotCalled() {
//given
final UUID uuid = UUID.randomUUID();
final AtomicBoolean supplierCalled = new AtomicBoolean(false);
final Supplier<UUID> supplier = () -> {
supplierCalled.set(true);
return uuid;
};
final Lazy<UUID> uuidLazy = Lazy.of(supplier);
//when
uuidLazy.map(UUID::toString);
//then
assertThat(supplierCalled).isFalse();
}
@Test
public void whenMapLazyValueThenSupplierIsCalled() {
//given
final UUID uuid = UUID.randomUUID();
final AtomicBoolean supplierCalled = new AtomicBoolean(false);
final Supplier<UUID> supplier = () -> {
supplierCalled.set(true);
return uuid;
};
final Lazy<UUID> uuidLazy = Lazy.of(supplier);
final Lazy<String> stringLazy = uuidLazy.map(UUID::toString);
//when
stringLazy.value();
//then
assertThat(supplierCalled).isTrue();
}
@Test
public void whenMapLazyValueThenValueIsCorrect() {
//given
final UUID uuid = UUID.randomUUID();
final AtomicBoolean supplierCalled = new AtomicBoolean(false);
final Supplier<UUID> supplier = () -> {
supplierCalled.set(true);
return uuid;
};
final Lazy<UUID> uuidLazy = Lazy.of(supplier);
final Lazy<String> stringLazy = uuidLazy.map(UUID::toString);
//when
final String value = stringLazy.value();
//then
assertThat(value).isEqualTo(uuid.toString());
}
}