Skip to content

Commit fe5c13c

Browse files
committed
Merge pull request functionaljava#151 from orionll/master
Replaced SoftReference with WeakReference for better performance
2 parents e2273a1 + 0906174 commit fe5c13c

File tree

12 files changed

+184
-71
lines changed

12 files changed

+184
-71
lines changed

core/src/main/java/fj/Monoid.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,18 @@ public A zero() {
8989
return zero;
9090
}
9191

92+
/**
93+
* Returns a value summed <code>n</code> times (<code>a + a + ... + a</code>)
94+
* @param n multiplier
95+
* @param a the value to multiply
96+
* @return <code>a</code> summed <code>n</code> times. If <code>n <= 0</code>, returns <code>zero()</code>
97+
*/
98+
public A multiply(final int n, final A a) {
99+
A m = zero();
100+
for (int i = 0; i < n; i++) { m = sum(m, a); }
101+
return m;
102+
}
103+
92104
/**
93105
* Sums the given values with right-fold.
94106
*

core/src/main/java/fj/Ord.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ public A min(final A a1, final A a2) {
157157
*/
158158
public final F<A, F<A, A>> min = curry((a, a1) -> min(a, a1));
159159

160+
public final Ord<A> reverse() { return ord(Function.flip(f)); }
161+
160162
/**
161163
* Returns an order instance that uses the given equality test and ordering function.
162164
*
@@ -343,14 +345,25 @@ public static <A, B> Ord<Validation<A, B>> validationOrd(final Ord<A> oa, final
343345
*/
344346
public static <A> Ord<List<A>> listOrd(final Ord<A> oa) {
345347
return ord(l1 -> l2 -> {
346-
if (l1.isEmpty())
347-
return l2.isEmpty() ? Ordering.EQ : Ordering.LT;
348-
else if (l2.isEmpty())
349-
return l1.isEmpty() ? Ordering.EQ : Ordering.GT;
350-
else {
351-
final Ordering c = oa.compare(l1.head(), l2.head());
352-
return c == Ordering.EQ ? listOrd(oa).f.f(l1.tail()).f(l2.tail()) : c;
348+
List<A> x1 = l1;
349+
List<A> x2 = l2;
350+
351+
while (x1.isNotEmpty() && x2.isNotEmpty()) {
352+
final Ordering o = oa.compare(x1.head(), x2.head());
353+
if (o == Ordering.LT || o == Ordering.GT) {
354+
return o;
353355
}
356+
x1 = x1.tail();
357+
x2 = x2.tail();
358+
}
359+
360+
if (x1.isEmpty() && x2.isEmpty()) {
361+
return Ordering.EQ;
362+
} else if (x1.isEmpty()) {
363+
return Ordering.LT;
364+
} else {
365+
return Ordering.GT;
366+
}
354367
});
355368
}
356369

core/src/main/java/fj/Ordering.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ public enum Ordering {
2323
GT;
2424

2525
public int toInt() { return ordinal() - 1 ; }
26+
27+
public Ordering reverse() {
28+
switch (this) {
29+
case LT: return GT;
30+
case GT: return LT;
31+
}
32+
return EQ;
33+
}
34+
2635
public static Ordering fromInt(int cmp) {
2736
return cmp == 0 ? EQ : cmp > 0 ? GT : LT;
2837
}

core/src/main/java/fj/P.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ public static <A> P1<A> p(final A a) {
3737
return a;
3838
}
3939
@Override public P1<A> memo() { return this; }
40+
@Override public P1<A> weakMemo() { return this; }
41+
@Override public P1<A> softMemo() { return this; }
4042
};
4143
}
4244

core/src/main/java/fj/P1.java

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package fj;
22

3+
import java.lang.ref.Reference;
34
import java.lang.ref.SoftReference;
5+
import java.lang.ref.WeakReference;
46

57
import fj.data.Array;
68
import fj.data.List;
@@ -208,53 +210,93 @@ public <X> P1<X> map(final F<A, X> f) {
208210
}
209211

210212
/**
211-
* Provides a memoising P1 that remembers its value.
212-
*
213-
* @return A P1 that calls this P1 once and remembers the value for subsequent calls.
214-
*/
215-
public P1<A> memo() {
216-
final P1<A> self = this;
217-
return new P1<A>() {
218-
private final Object latch = new Object();
219-
private volatile SoftReference<Option<A>> v = null;
220-
221-
@Override
222-
public A _1() {
223-
Option<A> o = v != null ? v.get() : null;
224-
if (o == null) {
225-
synchronized (latch) {
226-
o = v != null ? v.get() : null;
227-
if (o == null) {
228-
o = Option.some(self._1());
229-
v = new SoftReference<>(o);
230-
}
231-
}
232-
}
233-
return o.some();
234-
}
213+
* Returns a P1 that remembers its value.
214+
*
215+
* @return A P1 that calls this P1 once and remembers the value for subsequent calls.
216+
*/
217+
public P1<A> memo() { return new Memo<>(this); }
235218

236-
@Override
237-
public P1<A> memo() {
238-
return this;
239-
}
219+
/**
220+
* Like <code>memo</code>, but the memoized value is wrapped into a <code>WeakReference</code>
221+
*/
222+
public P1<A> weakMemo() { return new WeakReferenceMemo<>(this); }
240223

241-
};
242-
}
224+
/**
225+
* Like <code>memo</code>, but the memoized value is wrapped into a <code>SoftReference</code>
226+
*/
227+
public P1<A> softMemo() { return new SoftReferenceMemo<>(this); }
243228

244229
static <A> P1<A> memo(F<Unit, A> f) {
245230
return P.lazy(f).memo();
246231
}
247232

248-
/**
249-
* Returns a constant function that always uses this value.
250-
*
251-
* @return A constant function that always uses this value.
252-
*/
253-
public <B> F<B, A> constant() {
233+
static class Memo<A> extends P1<A> {
234+
private final P1<A> self;
235+
private volatile boolean initialized;
236+
private A value;
237+
238+
Memo(P1<A> self) { this.self = self; }
239+
240+
@Override public A _1() {
241+
if (!initialized) {
242+
synchronized (this) {
243+
if (!initialized) {
244+
A a = self._1();
245+
value = a;
246+
initialized = true;
247+
return a;
248+
}
249+
}
250+
}
251+
return value;
252+
}
253+
254+
@Override public P1<A> memo() { return this; }
255+
}
256+
257+
abstract static class ReferenceMemo<A> extends P1<A> {
258+
private final P1<A> self;
259+
private final Object latch = new Object();
260+
private volatile Reference<Option<A>> v = null;
261+
262+
ReferenceMemo(final P1<A> self) { this.self = self; }
254263

255-
return b -> P1.this._1();
264+
@Override public A _1() {
265+
Option<A> o = v != null ? v.get() : null;
266+
if (o == null) {
267+
synchronized (latch) {
268+
o = v != null ? v.get() : null;
269+
if (o == null) {
270+
o = Option.some(self._1());
271+
v = newReference(o);
272+
}
273+
}
274+
}
275+
return o.some();
256276
}
257277

278+
abstract Reference<Option<A>> newReference(Option<A> o);
279+
}
280+
281+
static class WeakReferenceMemo<A> extends ReferenceMemo<A> {
282+
WeakReferenceMemo(P1<A> self) { super(self); }
283+
@Override Reference<Option<A>> newReference(final Option<A> o) { return new WeakReference<>(o); }
284+
@Override public P1<A> weakMemo() { return this; }
285+
}
286+
287+
static class SoftReferenceMemo<A> extends ReferenceMemo<A> {
288+
SoftReferenceMemo(P1<A> self) { super(self); }
289+
@Override Reference<Option<A>> newReference(final Option<A> o) { return new SoftReference<>(o); }
290+
@Override public P1<A> softMemo() { return this; }
291+
}
292+
293+
/**
294+
* Returns a constant function that always uses this value.
295+
*
296+
* @return A constant function that always uses this value.
297+
*/
298+
public <B> F<B, A> constant() { return Function.constant(_1()); }
299+
258300
@Override
259301
public String toString() {
260302
return Show.p1Show(Show.<A>anyShow()).showS(this);

core/src/main/java/fj/data/List.java

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
import fj.P2;
1616
import fj.Show;
1717
import fj.Unit;
18-
import static fj.Function.curry;
19-
import static fj.Function.constant;
20-
import static fj.Function.identity;
21-
import static fj.Function.compose;
18+
19+
import static fj.Function.*;
2220
import static fj.P.p;
2321
import static fj.P.p2;
2422
import static fj.Unit.unit;
@@ -169,9 +167,10 @@ public final Stream<A> toStream() {
169167
*/
170168
@SuppressWarnings({"unchecked"})
171169
public final Array<A> toArray() {
172-
final Object[] a = new Object[length()];
170+
final int length = length();
171+
final Object[] a = new Object[length];
173172
List<A> x = this;
174-
for (int i = 0; i < length(); i++) {
173+
for (int i = 0; i < length; i++) {
175174
a[i] = x.head();
176175
x = x.tail();
177176
}
@@ -625,18 +624,18 @@ public final <B> List<B> apply(final List<F<A, B>> lf) {
625624
* @return A new list that has appended the given list.
626625
*/
627626
public final List<A> append(final List<A> as) {
628-
return fromList(this).append(as).toList();
627+
return Buffer.fromList(this).prependToList(as);
629628
}
630629

631630
/**
632-
* Performs a right-fold reduction across this list. This function uses O(length) stack space.
631+
* Performs a right-fold reduction across this list.
633632
*
634633
* @param f The function to apply on each element of the list.
635634
* @param b The beginning value to start the application from.
636635
* @return The final result after the right-fold reduction.
637636
*/
638637
public final <B> B foldRight(final F<A, F<B, B>> f, final B b) {
639-
return isEmpty() ? b : f.f(head()).f(tail().foldRight(f, b));
638+
return reverse().foldLeft(flip(f), b);
640639
}
641640

642641
/**
@@ -1581,7 +1580,9 @@ public static <A, B> P2<List<A>, List<B>> unzip(final List<P2<A, B>> xs) {
15811580
* @return A list of the given value replicated the given number of times.
15821581
*/
15831582
public static <A> List<A> replicate(final int n, final A a) {
1584-
return n <= 0 ? List.<A>nil() : replicate(n - 1, a).cons(a);
1583+
List<A> list = List.nil();
1584+
for (int i = 0; i < n; i++) { list = list.cons(a); }
1585+
return list;
15851586
}
15861587

15871588
/**
@@ -1786,7 +1787,7 @@ public Iterator<A> iterator() {
17861787
* Appends (snoc) the given element to this buffer to produce a new buffer.
17871788
*
17881789
* @param a The element to append to this buffer.
1789-
* @return A new buffer with the given element appended.
1790+
* @return This buffer.
17901791
*/
17911792
public Buffer<A> snoc(final A a) {
17921793
if (exported)
@@ -1805,10 +1806,10 @@ public Buffer<A> snoc(final A a) {
18051806
}
18061807

18071808
/**
1808-
* Appends the given buffer to this buffer.
1809+
* Appends the given list to this buffer.
18091810
*
1810-
* @param as The buffer to append to this one.
1811-
* @return A new buffer that has appended the given buffer.
1811+
* @param as The list to append to this buffer.
1812+
* @return This buffer.
18121813
*/
18131814
public Buffer<A> append(final List<A> as) {
18141815
for (List<A> xs = as; xs.isNotEmpty(); xs = xs.tail())
@@ -1817,6 +1818,28 @@ public Buffer<A> append(final List<A> as) {
18171818
return this;
18181819
}
18191820

1821+
/**
1822+
* Prepends the elements of this buffer to the given list.
1823+
*
1824+
* @param as the list to which elements are prepended.
1825+
*/
1826+
public List<A> prependToList(final List<A> as) {
1827+
if (isEmpty()) {
1828+
return as;
1829+
} else {
1830+
if (exported)
1831+
copy();
1832+
1833+
tail.tail(as);
1834+
return toList();
1835+
}
1836+
}
1837+
1838+
/**
1839+
* Returns <code>true</code> if this buffer is empty, <code>false</code> otherwise.
1840+
*/
1841+
public boolean isEmpty() { return start.isEmpty(); }
1842+
18201843
/**
18211844
* Returns an immutable list projection of this buffer. Modifications to the underlying buffer
18221845
* will <em>not</em> be reflected in returned lists.

core/src/main/java/fj/data/Stream.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,10 @@ public final A index(final int i) {
12331233
* <code>false</code> otherwise.
12341234
*/
12351235
public final boolean forall(final F<A, Boolean> f) {
1236-
return isEmpty() || f.f(head()) && tail()._1().forall(f);
1236+
for (final A a : this) {
1237+
if (!f.f(a)) return false;
1238+
}
1239+
return true;
12371240
}
12381241

12391242
@Override
@@ -1430,7 +1433,7 @@ private static final class Cons<A> extends Stream<A> {
14301433

14311434
Cons(final A head, final P1<Stream<A>> tail) {
14321435
this.head = head;
1433-
this.tail = tail.memo();
1436+
this.tail = tail.weakMemo();
14341437
}
14351438

14361439
public A head() {

props-core/src/test/java/fj/data/properties/ListProperties.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package fj.data.properties;
22

3+
import fj.Ord;
34
import fj.P;
45
import fj.P2;
56
import fj.data.List;
7+
import fj.test.reflect.CheckParams;
68
import fj.test.runner.PropertyTestRunner;
79
import fj.test.Gen;
810
import fj.test.Property;
@@ -18,6 +20,7 @@
1820
* Created by Zheka Kozlov on 02.06.2015.
1921
*/
2022
@RunWith(PropertyTestRunner.class)
23+
@CheckParams(maxSize = 10000)
2124
public class ListProperties {
2225

2326
public Property isPrefixOf() {
@@ -53,4 +56,14 @@ public Property isSuffixOfDifferentHeads() {
5356
return property(arbList(arbInteger), arbList(arbInteger), arbInteger, arbInteger, (list1, list2, h1, h2) ->
5457
implies(intEqual.notEq(h1, h2), () -> prop(!list1.snoc(h1).isSuffixOf(intEqual, list2.snoc(h2)))));
5558
}
59+
60+
public Property listOrdEqual() {
61+
return property(arbList(arbInteger), list -> prop(Ord.listOrd(Ord.intOrd).equal().eq(list, list)));
62+
}
63+
64+
public Property listOrdReverse() {
65+
final Ord<List<Integer>> ord = Ord.listOrd(Ord.intOrd);
66+
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
67+
prop(ord.compare(list1, list2) == ord.reverse().compare(list1, list2).reverse()));
68+
}
5669
}

0 commit comments

Comments
 (0)