Skip to content

Commit e5bea13

Browse files
committed
Map: refactoring towards #filter pattern
MappingIterator: lazy mapping logic Also adding various test harness plumbing for better testing semantics
1 parent e94d839 commit e5bea13

9 files changed

Lines changed: 167 additions & 29 deletions

File tree

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,24 @@
11
package com.jnape.palatable.lambda.functions;
22

3+
import com.jnape.palatable.lambda.DyadicFunction;
34
import com.jnape.palatable.lambda.MonadicFunction;
4-
import com.jnape.palatable.lambda.iterators.ImmutableIterator;
5+
import com.jnape.palatable.lambda.iterators.MappingIterator;
56

67
import java.util.Iterator;
78

8-
public class Map {
9+
public class Map<A, B> extends DyadicFunction<MonadicFunction<? super A, ? extends B>, Iterable<A>, Iterable<B>> {
910

10-
public static <A, B> Iterable<B> map(final MonadicFunction<? super A, ? extends B> function, final Iterable<A> as) {
11+
@Override
12+
public Iterable<B> apply(final MonadicFunction<? super A, ? extends B> function, final Iterable<A> as) {
1113
return new Iterable<B>() {
1214
@Override
1315
public Iterator<B> iterator() {
14-
final Iterator<A> asIterator = as.iterator();
15-
return new ImmutableIterator<B>() {
16-
@Override
17-
public boolean hasNext() {
18-
return asIterator.hasNext();
19-
}
20-
21-
@Override
22-
public B next() {
23-
return function.apply(asIterator.next());
24-
}
25-
};
16+
return new MappingIterator<A, B>(function, as.iterator());
2617
}
2718
};
2819
}
2920

21+
public static <A, B> Iterable<B> map(final MonadicFunction<? super A, ? extends B> function, final Iterable<A> as) {
22+
return new Map<A, B>().apply(function, as);
23+
}
3024
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.jnape.palatable.lambda.iterators;
2+
3+
import com.jnape.palatable.lambda.MonadicFunction;
4+
5+
import java.util.Iterator;
6+
7+
public class MappingIterator<A, B> extends ImmutableIterator<B> {
8+
9+
private final MonadicFunction<? super A, ? extends B> function;
10+
private final Iterator<A> iterator;
11+
12+
public MappingIterator(MonadicFunction<? super A, ? extends B> function, Iterator<A> iterator) {
13+
this.function = function;
14+
this.iterator = iterator;
15+
}
16+
17+
@Override
18+
public boolean hasNext() {
19+
return iterator.hasNext();
20+
}
21+
22+
@Override
23+
public B next() {
24+
return function.apply(iterator.next());
25+
}
26+
}
Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
package com.jnape.palatable.lambda.functions;
22

3-
import com.jnape.palatable.lambda.MonadicFunction;
3+
import com.jnape.palatable.lambda.iterators.MappingIterator;
44
import org.junit.Test;
55

6+
import static com.jnape.palatable.lambda.builtin.monadic.Always.always;
67
import static com.jnape.palatable.lambda.functions.Map.map;
78
import static java.util.Arrays.asList;
9+
import static org.hamcrest.CoreMatchers.instanceOf;
10+
import static org.hamcrest.core.Is.is;
811
import static org.junit.Assert.assertThat;
9-
import static testsupport.matchers.IterableMatcher.iterates;
1012

1113
public class MapTest {
1214

1315
@Test
14-
public void mapsInputsIntoOutputs() {
15-
Iterable<String> strings = asList("one", "two", "three");
16-
Iterable<Integer> stringsToLengths = map(new MonadicFunction<String, Integer>() {
17-
@Override
18-
public Integer apply(String string) {
19-
return string.length();
20-
}
21-
}, strings);
22-
23-
assertThat(stringsToLengths, iterates(3, 3, 5));
16+
public void producesMappingIterator() {
17+
assertThat(
18+
map(always(true), asList("a", "b", "c")).iterator(),
19+
is(instanceOf(MappingIterator.class))
20+
);
2421
}
2522
}

src/test/java/com/jnape/palatable/lambda/iterators/ImmutableIteratorTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import org.junit.Before;
44
import org.junit.Test;
55

6+
import static testsupport.exceptions.OutOfScopeException.outOfScope;
7+
68
public class ImmutableIteratorTest {
79

810
private ImmutableIterator immutableIterator;
@@ -12,12 +14,12 @@ public void setUp() {
1214
immutableIterator = new ImmutableIterator() {
1315
@Override
1416
public boolean hasNext() {
15-
return false;
17+
throw outOfScope();
1618
}
1719

1820
@Override
1921
public Object next() {
20-
return null;
22+
throw outOfScope();
2123
}
2224
};
2325
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.jnape.palatable.lambda.iterators;
2+
3+
import com.jnape.palatable.lambda.MonadicFunction;
4+
import org.junit.Test;
5+
6+
import static com.jnape.palatable.lambda.builtin.monadic.Identity.id;
7+
import static com.jnape.palatable.lambda.functions.Map.map;
8+
import static com.jnape.palatable.lambda.staticfactory.IterableFactory.iterable;
9+
import static org.junit.Assert.assertThat;
10+
import static testsupport.Mocking.mockIterable;
11+
import static testsupport.matchers.IterableMatcher.isEmpty;
12+
import static testsupport.matchers.IterableMatcher.iterates;
13+
import static testsupport.matchers.ZeroInvocationsMatcher.wasNeverInteractedWith;
14+
15+
public class MappingIteratorTest {
16+
17+
@Test
18+
public void mapsInputsIntoOutputs() {
19+
MonadicFunction<String, Integer> length = new MonadicFunction<String, Integer>() {
20+
@Override
21+
public Integer apply(String string) {
22+
return string.length();
23+
}
24+
};
25+
assertThat(
26+
map(length, iterable("one", "two", "three")),
27+
iterates(3, 3, 5)
28+
);
29+
}
30+
31+
@Test
32+
@SuppressWarnings("unchecked")
33+
public void worksOnEmptyIterables() {
34+
assertThat(map(id(), iterable()), isEmpty());
35+
}
36+
37+
@Test
38+
public void defersIteration() {
39+
Iterable<Object> iterable = mockIterable();
40+
map(id(), iterable);
41+
assertThat(iterable, wasNeverInteractedWith());
42+
assertThat(iterable.iterator(), wasNeverInteractedWith());
43+
}
44+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package testsupport;
2+
3+
import java.util.Iterator;
4+
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.when;
7+
8+
public class Mocking {
9+
10+
@SuppressWarnings("unchecked")
11+
public static <A> Iterable<A> mockIterable() {
12+
Iterable<A> iterable = (Iterable<A>) mock(Iterable.class);
13+
when(iterable.iterator()).thenReturn((Iterator<A>) mock(Iterator.class));
14+
return iterable;
15+
}
16+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package testsupport.exceptions;
2+
3+
public class OutOfScopeException extends RuntimeException {
4+
5+
public OutOfScopeException(String s) {
6+
super(s);
7+
}
8+
9+
public static OutOfScopeException outOfScope() {
10+
return new OutOfScopeException("Unexpected invocation of unimplemented method");
11+
}
12+
}

src/test/java/testsupport/matchers/IterableMatcher.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
import org.hamcrest.BaseMatcher;
44
import org.hamcrest.Description;
5+
import org.hamcrest.Matcher;
56

7+
import java.util.ArrayList;
68
import java.util.Iterator;
79

810
import static java.util.Arrays.asList;
@@ -70,4 +72,8 @@ private String stringify(Iterable iterable) {
7072
public static <Element> IterableMatcher<Element> iterates(Element... elements) {
7173
return new IterableMatcher<Element>(asList(elements));
7274
}
75+
76+
public static <A> Matcher<Iterable<A>> isEmpty() {
77+
return new IterableMatcher<A>(new ArrayList<A>());
78+
}
7379
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package testsupport.matchers;
2+
3+
import org.hamcrest.BaseMatcher;
4+
import org.hamcrest.Description;
5+
import org.hamcrest.Matcher;
6+
import org.mockito.exceptions.misusing.NotAMockException;
7+
import org.mockito.exceptions.verification.NoInteractionsWanted;
8+
import org.mockito.internal.util.MockUtil;
9+
import org.mockito.invocation.Invocation;
10+
11+
import static org.mockito.Mockito.verifyNoMoreInteractions;
12+
13+
public class ZeroInvocationsMatcher<T> extends BaseMatcher<T> {
14+
@Override
15+
public boolean matches(Object item) {
16+
try {
17+
verifyNoMoreInteractions(item);
18+
return true;
19+
} catch (NoInteractionsWanted unexpectedInteractions) {
20+
return false;
21+
} catch (NotAMockException notVerifiable) {
22+
throw new AssertionError("Can't do verifications on non-mocked objects.");
23+
}
24+
}
25+
26+
@Override
27+
public void describeMismatch(Object item, final Description description) {
28+
description.appendText("had these: ");
29+
for (Invocation invocation : new MockUtil().getMockHandler(item).getInvocationContainer().getInvocations())
30+
description.appendText(invocation.toString());
31+
}
32+
33+
@Override
34+
public void describeTo(Description description) {
35+
description.appendText("no interactions with mock");
36+
}
37+
38+
public static <T> Matcher<T> wasNeverInteractedWith() {
39+
return new ZeroInvocationsMatcher<T>();
40+
}
41+
}

0 commit comments

Comments
 (0)