Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Anamorphisms #5

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
</developers>

<properties>
<lambda.version>4.0.0</lambda.version>
<traitor.version>1.2</traitor.version>
<lambda.version>5.1.0</lambda.version>
<traitor.version>1.4.0</traitor.version>
<maven-compiler-plugin.version>3.3</maven-compiler-plugin.version>
<hamcrest-all.version>1.3</hamcrest-all.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/com/jnape/palatable/ouroboros/Anamorphism.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.jnape.palatable.ouroboros;

import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.Fn2;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.functor.builtin.Lazy;

import static com.jnape.palatable.ouroboros.Fix.fix;

/**
* An {@link Anamorphism} uses a {@link Coalgebra} to build up structure:
* <pre>
* {@code
* Coalgebra<Integer, Maybe<Integer>> generateToSize = x -> x < 3 ? just(x + 1) : nothing();
* ana(generateToSize, 0).unfix(); // Just fix(Just fix(Just fix(Nothing)))
* }
* </pre>
*
* @param <A> the carrier type
* @param <F> the {@link Functor} witness
* @param <FA> the {@link Functor} F&lt;{@link Lazy}&lt;A&gt;&gt;
*/
public final class Anamorphism<A,
F extends Functor<?, F>,
FA extends Functor<A, F>>
implements Fn2<Coalgebra<A, FA>, A, Fix<F, ? extends Functor<? extends Fix<F, ?>, F>>> {
private static final Anamorphism<?, ?, ?> INSTANCE = new Anamorphism<>();

private Anamorphism() {
}

@Override
public Fix<F, ? extends Functor<? extends Fix<F, ?>, F>> checkedApply(Coalgebra<A, FA> coalgebra, A a) throws Throwable {
return fix(coalgebra.apply(a).fmap(ana(coalgebra)));
}

@SuppressWarnings("unchecked")
public static <A,
F extends Functor<?, F>,
FA extends Functor<A, F>> Anamorphism<A, F, FA> ana() {
return (Anamorphism<A, F, FA>) INSTANCE;
}

public static <A,
F extends Functor<?, F>,
FA extends Functor<A, F>> Fn1<A, Fix<F, ? extends Functor<? extends Fix<F, ?>, F>>> ana(Coalgebra<A, FA> coalgebra) {
return Anamorphism.<A, F, FA>ana().apply(coalgebra);
}

public static <A,
F extends Functor<?, F>,
FA extends Functor<A, F>> Fix<F, ? extends Functor<? extends Fix<F, ?>, F>> ana(Coalgebra<A, FA> coalgebra,
A a) {
return ana(coalgebra).apply(a);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/jnape/palatable/ouroboros/Coalgebra.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.jnape.palatable.ouroboros;

import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functor.Functor;

/**
* A <code>{@link Coalgebra}&lt;A, F&gt;</code> for some carrier type <code>A</code> and some {@link Functor}
* <code>F</code> is a morphism <code>A -&gt; F&lt;A&gt;</code>.
*
* @param <A> the carrier type
* @param <F> the {@link Functor} witness
*/
@FunctionalInterface
public interface Coalgebra<A, F extends Functor<A, ?>> extends Fn1<A, F> {
}
39 changes: 39 additions & 0 deletions src/main/java/com/jnape/palatable/ouroboros/FixLazy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.jnape.palatable.ouroboros;

import com.jnape.palatable.lambda.functions.Fn0;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.functor.builtin.Lazy;

import java.util.Objects;

import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;

public interface FixLazy<F extends Functor<?, F>, Unfixed extends Functor<? extends FixLazy<F, ?>, F>> {

Lazy<Unfixed> unfixLazy();

static <F extends Functor<?, F>, Unfixed extends Functor<? extends FixLazy<F, ?>, F>> FixLazy<F, ? extends Unfixed> fixLazy(
Fn0<Unfixed> unfixed) {
return new FixLazy<F, Unfixed>() {
@Override
public Lazy<Unfixed> unfixLazy() {
return lazy(unfixed);
}

@Override
public boolean equals(Object obj) {
return (obj instanceof FixLazy) && Objects.equals(unfixed, ((FixLazy) obj).unfixLazy());
}

@Override
public int hashCode() {
return 31 * Objects.hashCode(unfixed);
}

@Override
public String toString() {
return "fix(" + unfixed + ")";
}
};
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/jnape/palatable/ouroboros/LazyAnamorphism.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.jnape.palatable.ouroboros;

import com.jnape.palatable.lambda.functions.Fn1;
import com.jnape.palatable.lambda.functions.Fn2;
import com.jnape.palatable.lambda.functions.builtin.fn2.LazyRec;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.functor.builtin.Lazy;

import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;

/**
* A LazyAnamorphism builds up structure using a {@link Coalgebra}. Unlike an {@link Anamorphism}, a LazyAnamorphism may
* recurse infinitely.
*
* @param <A> the carrier type
* @param <F> the {@link Functor} witness
* @param <FA> the {@link Functor} F&lt;{@link Lazy}&lt;A&gt;&gt;
*/
public class LazyAnamorphism<A,
F extends Functor<?, F>,
FA extends Functor<A, F>>
implements Fn2<Coalgebra<A, FA>, A, FixLazy<F, ? extends Functor<? extends FixLazy<F, ?>, F>>> {
private static final LazyAnamorphism<?, ?, ?> INSTANCE = new LazyAnamorphism<>();

private LazyAnamorphism() {
}

@Override
public FixLazy<F, ? extends Functor<? extends FixLazy<F, ?>, F>> checkedApply(Coalgebra<A, FA> coalgebra, A a) throws Throwable {
return LazyRec.<A, FixLazy<F, ? extends Functor<? extends FixLazy<F, ?>, F>>>lazyRec(
(f, anotherA) -> lazy(() -> FixLazy.<F, Functor<? extends FixLazy<F, ?>, F>>fixLazy(() -> coalgebra.apply(anotherA)
.fmap(f)
.fmap(Lazy::value))),
a).value();
}

@SuppressWarnings("unchecked")
public static <A,
F extends Functor<?, F>,
FA extends Functor<A, F>>
LazyAnamorphism<A, F, FA> lazyAna() {
return (LazyAnamorphism<A, F, FA>) INSTANCE;
}

public static <A,
F extends Functor<?, F>,
FA extends Functor<A, F>>
Fn1<A, FixLazy<F, ? extends Functor<? extends FixLazy<F, ?>, F>>> lazyAna(Coalgebra<A, FA> coalgebra) {
return LazyAnamorphism.<A, F, FA>lazyAna().apply(coalgebra);
}

public static <A,
F extends Functor<?, F>,
FA extends Functor<A, F>>
FixLazy<F, ? extends Functor<? extends FixLazy<F, ?>, F>> lazyAna(Coalgebra<A, FA> coalgebra, A a) {
return lazyAna(coalgebra).apply(a);
}
}
66 changes: 66 additions & 0 deletions src/test/java/com/jnape/palatable/ouroboros/AnamorphismTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.jnape.palatable.ouroboros;

import com.jnape.palatable.lambda.adt.Maybe;
import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.lambda.functions.builtin.fn2.Cons;
import com.jnape.palatable.lambda.functor.Functor;
import com.jnape.palatable.lambda.functor.builtin.Lazy;
import com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT;
import org.junit.Test;

import java.util.ArrayList;

import static com.jnape.palatable.lambda.adt.Maybe.just;
import static com.jnape.palatable.lambda.adt.Maybe.nothing;
import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Constantly.constantly;
import static com.jnape.palatable.lambda.functions.builtin.fn1.Head.head;
import static com.jnape.palatable.lambda.functions.builtin.fn2.ToCollection.toCollection;
import static com.jnape.palatable.lambda.functor.builtin.Lazy.lazy;
import static com.jnape.palatable.lambda.monad.transformer.builtin.MaybeT.maybeT;
import static com.jnape.palatable.ouroboros.Anamorphism.ana;
import static com.jnape.palatable.ouroboros.Catamorphism.cata;
import static com.jnape.palatable.ouroboros.Fix.fix;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;

public class AnamorphismTest {

@Test
public void fromMaybe() {
Coalgebra<Integer, Maybe<Integer>> generateToSize = x -> x < 3 ? just(x + 1) : nothing();
Functor<? extends Fix<Maybe<?>, ?>, Maybe<?>> unfix = ana(generateToSize, 0).unfix();

assertEquals(unfix, just(fix(just(fix(just(fix(nothing())))))));
}

@Test
public void collatzFromTuple() {
Coalgebra<Integer, MaybeT<Tuple2<Integer, ?>, Integer>> coalgebra = x -> x == 1 ? maybeT(tuple(x, nothing()))
: x % 2 == 0 ? maybeT(tuple(x, just(x / 2))) : maybeT(tuple(x, just(3 * x + 1)));

assertEquals(ana(coalgebra, 4).unfix(),
maybeT(tuple(4, just(fix(maybeT(tuple(2, just(fix(maybeT(tuple(1, nothing())))))))))));
}

@Test
public void collatzToCata() {
Coalgebra<Lazy<Iterable<Integer>>, MaybeT<Tuple2<Integer, ?>, Lazy<Iterable<Integer>>>> coalgebra = x -> {
Integer value = head(x.value()).orElse(1);
return value == 1 ? maybeT(tuple(value, nothing()))
: value % 2 == 0 ? maybeT(tuple(value, just(lazy(singletonList(value / 2))))) : maybeT(tuple(value, just(lazy(singletonList(3 * value + 1)))));
};

Algebra<MaybeT<Tuple2<Integer, ?>, Lazy<Iterable<Integer>>>, Lazy<Iterable<Integer>>> algebra = mtii -> {
Tuple2<Integer, Maybe<Lazy<Iterable<Integer>>>> run = mtii.run();
return lazy(Cons.cons(run._1(), run._2().match(constantly(emptyList()),
Lazy::value)));
};

assertEquals(toCollection(ArrayList::new, cata(algebra, ana(coalgebra, lazy(singletonList(3)))).value()),
asList(3, 10, 5, 16, 8, 4, 2, 1));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jnape.palatable.ouroboros;

import com.jnape.palatable.lambda.adt.hlist.Tuple2;
import com.jnape.palatable.lambda.functor.Functor;
import org.junit.Test;

import static com.jnape.palatable.lambda.adt.hlist.HList.tuple;
import static com.jnape.palatable.ouroboros.LazyAnamorphism.lazyAna;

public class LazyAnamorphismTest {
@Test
public void doesntStackOverflow() {
Coalgebra<Integer, Tuple2<Integer, Integer>> coalgebra = x -> x % 2 == 0 ? tuple(x, x / 2) : tuple(x, 3 * x + 1);

FixLazy<Tuple2<Integer, ?>, ? extends Functor<? extends FixLazy<Tuple2<Integer, ?>, ?>, Tuple2<Integer, ?>>> ana = lazyAna(coalgebra, 15);
ana.unfixLazy();
}
}