Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 66 additions & 5 deletions core/src/main/java/fj/Equal.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,39 @@ public interface Definition<A> {
default boolean equal(A a1, A a2) {
return equal(a1).f(a2);
}

/**
* Refine this equal definition, to tests equality of self and the mapped object in "and" manner.
* @see #equal()
*
* @param f The function to map the original object
* @param eq Equality for the mapped object
* @return A new equal definition
*/
default <B> Definition<A> then(final F<A, B> f, final Equal<B> eq) {
Definition<B> bEqDef = eq.def;
return new Definition<A>() {
@Override
public F<A, Boolean> equal(A a1) {
F<A, Boolean> fa = Definition.this.equal(a1);
F<B, Boolean> fb = bEqDef.equal(f.f(a1));
return a2 -> fa.f(a2) && fb.f(f.f(a2));
}

@Override
public boolean equal(A a1, A a2) {
return Definition.this.equal(a1, a2) && bEqDef.equal(f.f(a1), f.f(a2));
}
};
}

/**
* Build an equal instance from this definition.
* to be called after some successive {@link #then(F, Equal)} calls.
*/
default Equal<A> equal() {
return equalDef(this);
}
}

/**
Expand Down Expand Up @@ -118,18 +151,46 @@ public F<A, Boolean> eq(final A a) {
* @return A new equal.
*/
public <B> Equal<B> contramap(final F<B, A> f) {
Definition<A> eaDef = def;
return equalDef(new Definition<B>(){
return equalDef(contramapDef(f, def));
}

/**
* An equal instance, which reverts equality for self
*
* @return A new equal instance
*/
public final Equal<A> not() {
return equalDef((a1, a2) -> !def.equal(a1, a2));
}


private static <A, B> Definition<B> contramapDef(F<B, A> f, Definition<A> aEqDef) {
return new Definition<B>(){
@Override
public F<B, Boolean> equal(B b) {
return compose(eaDef.equal(f.f(b)), f);
return compose(aEqDef.equal(f.f(b)), f);
}

@Override
public boolean equal(B b1, B b2) {
return eaDef.equal(f.f(b1), f.f(b2));
return aEqDef.equal(f.f(b1), f.f(b2));
}
});
};
}

/**
* Static version of {@link #contramap(F)}
*/
public static <A, B> Equal<A> contramap(final F<A, B> f, final Equal<B> eq) {
return eq.contramap(f);
}

/**
* Begin definition of an equal instance.
* @see Definition#then(F, Equal)
*/
public static <A, B> Definition<A> on(final F<A, B> f, final Equal<B> eq) {
return contramapDef(f, eq.def);
}

/**
Expand Down
88 changes: 70 additions & 18 deletions core/src/main/java/fj/Ord.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,42 @@ public Definition<A> dual() {
}
};
}

/**
* Refine this ord definition: compares using self and if objects are equal compares using given <code>Ord</code>.
* @see #ord()
*
* @param bOrd Ord for subsequent comparison
* @return A new ord definition.
*/
default <B> Definition<A> then(final F<A, B> f, final Ord<B> bOrd) {
Definition<B> bOrdDef = bOrd.def;
return new Definition<A>() {
@Override
public F<A, Ordering> compare(A a1) {
F<A, Ordering> fa = Definition.this.compare(a1);
F<B, Ordering> fb = bOrdDef.compare(f.f(a1));
return a2 -> {
Ordering aOrdering = fa.f(a2);
return aOrdering != Ordering.EQ ? aOrdering : fb.f(f.f(a2));
};
}

@Override
public Ordering compare(A a1, A a2) {
Ordering aOrdering = Definition.this.compare(a1, a2);
return aOrdering != Ordering.EQ ? aOrdering : bOrdDef.compare(f.f(a1), f.f(a2));
}
};
}

/**
* Build an ord instance from this definition.
* to be called after some successive {@link #then(F, Ord)} calls.
*/
default Ord<A> ord() {
return ordDef(this);
}
}

/**
Expand Down Expand Up @@ -160,18 +196,7 @@ public Equal<A> equal() {
* @return A new ord.
*/
public <B> Ord<B> contramap(final F<B, A> f) {
Definition<A> selfDef = def;
return ordDef(new Definition<B>() {
@Override
public F<B, Ordering> compare(B b) {
return compose(selfDef.compare(f.f(b)), f);
}

@Override
public Ordering compare(B b1, B b2) {
return selfDef.compare(f.f(b1), f.f(b2));
}
});
return ordDef(contramapDef(f, def));
}

/**
Expand Down Expand Up @@ -287,6 +312,35 @@ public final Ord<A> reverse() {
return ordDef(def.dual());
}

/**
* Begin definition of an ord instance.
* @see Definition#then(F, Equal)
*/
public static <A, B> Definition<A> on(final F<A, B> f, final Ord<B> ord) {
return contramapDef(f, ord.def);
}

/**
* Static version of {@link #contramap(F)}
*/
public static <A, B> Ord<A> contramap(final F<A, B> f, final Ord<B> ord) {
return ordDef(contramapDef(f, ord.def));
}

private static <A, B> Definition<B> contramapDef(F<B, A> f, Definition<A> def) {
return new Definition<B>() {
@Override
public F<B, Ordering> compare(B b) {
return compose(def.compare(f.f(b)), f);
}

@Override
public Ordering compare(B b1, B b2) {
return def.compare(f.f(b1), f.f(b2));
}
};
}

/**
* Returns an order instance that uses the given equality test and ordering function.
*
Expand Down Expand Up @@ -576,15 +630,15 @@ public static <A> Ord<P1<A>> p1Ord(final Ord<A> oa) {
* @return An order instance for a product-2, with the first factor considered most significant.
*/
public static <A, B> Ord<P2<A, B>> p2Ord(final Ord<A> oa, final Ord<B> ob) {
return ordDef((a, b) -> oa.eq(a._1(), b._1()) ? ob.compare(a._2(), b._2()) : oa.compare(a._1(), b._1()));
return on(P2.<A, B>__1(), oa).then(P2.__2(), ob).ord();
}

public static <A, B> Ord<P2<A, B>> p2Ord1(Ord<A> oa) {
return ordDef((p1, p2) -> oa.compare(p1._1(), p2._1()));
return on(P2.<A, B>__1(), oa).ord();
}

public static <A, B> Ord<P2<A, B>> p2Ord2(Ord<B> ob) {
return ordDef((p1, p2) -> ob.compare(p1._2(), p2._2()));
return on(P2.<A, B>__2(), ob).ord();
}

/**
Expand All @@ -596,9 +650,7 @@ public static <A, B> Ord<P2<A, B>> p2Ord2(Ord<B> ob) {
* @return An order instance for a product-3, with the first factor considered most significant.
*/
public static <A, B, C> Ord<P3<A, B, C>> p3Ord(final Ord<A> oa, final Ord<B> ob, final Ord<C> oc) {
return ordDef((a, b) -> oa.eq(a._1(), b._1()) ?
p2Ord(ob, oc).compare(P.p(a._2(), a._3()), P.p(b._2(), b._3()))
: oa.compare(a._1(), b._1()));
return on(P3.<A, B, C>__1(), oa).then(P3.__2(), ob).then(P3.__3(), oc).ord();
}

/**
Expand Down
26 changes: 26 additions & 0 deletions core/src/test/java/fj/EqualTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package fj;

import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class EqualTest {
@Test
public void contramapShouldWork() {
Equal<String> equalByLength = Equal.contramap(String::length, Equal.intEqual);

assertThat(equalByLength.eq("str1", "str2"), is(true));
assertThat(equalByLength.eq("str1", "str11"), is(false));
}

@Test
public void thenShouldWork() {
Equal<String> equalByLengthThenLastDigit = Equal.on(String::length, Equal.intEqual)
.then(s -> s.charAt(s.length() - 1), Equal.charEqual).equal();

assertThat(equalByLengthThenLastDigit.eq("str1", "spr1"), is(true));
assertThat(equalByLengthThenLastDigit.eq("str1", "str2"), is(false));
assertThat(equalByLengthThenLastDigit.eq("str1", "strr1"), is(false));
}
}
18 changes: 18 additions & 0 deletions core/src/test/java/fj/OrdTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,22 @@ public void isLessThan() {
assertThat(pred.f(1L), is(false));
assertThat(pred.f(2L), is(false));
}

@Test
public void contramapShouldWork() {
Ord<String> lengthOrd = Ord.contramap(String::length, Ord.intOrd);

assertThat(lengthOrd.compare("str", "rts"), is(Ordering.EQ));
assertThat(lengthOrd.compare("strlong", "str"), is(Ordering.GT));
}

@Test
public void thenShouldWork() {
Ord<String> lengthThenLastDigitOrd = Ord.on(String::length, Ord.intOrd)
.then(s -> s.charAt(s.length() - 1), Ord.charOrd).ord();

assertThat(lengthThenLastDigitOrd.compare("str", "dyr"), is(Ordering.EQ));
assertThat(lengthThenLastDigitOrd.compare("stt", "str"), is(Ordering.GT));
assertThat(lengthThenLastDigitOrd.compare("str", "strr"), is(Ordering.LT));
}
}