org.opencastproject.util.data.Monadics Maven / Gradle / Ivy
/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.util.data;
import static java.lang.StrictMath.min;
import static java.util.Arrays.asList;
import static org.opencastproject.util.data.Collections.appendTo;
import static org.opencastproject.util.data.Collections.appendToA;
import static org.opencastproject.util.data.Collections.appendToM;
import static org.opencastproject.util.data.Collections.forc;
import static org.opencastproject.util.data.Collections.iterator;
import static org.opencastproject.util.data.Collections.list;
import static org.opencastproject.util.data.Collections.toList;
import static org.opencastproject.util.data.Option.none;
import static org.opencastproject.util.data.Option.some;
import static org.opencastproject.util.data.Prelude.unexhaustiveMatch;
import static org.opencastproject.util.data.Tuple.tuple;
import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public final class Monadics {
private Monadics() {
}
// we need to define a separate interface for each container type
// since Java lacks higher-order polymorphism (higher-kinded type) so we cannot
// abstract over the container type like this
//
// interface Functor> {
// F fmap(F a, Function f);
// }
//
// or
//
// interface Functor> {
// F fmap(Function f);
// }
/** The list monad. */
public abstract static class ListMonadic implements Iterable {
private ListMonadic() {
}
/** Alias for {@link #fmap(Function) fmap}. */
public final ListMonadic map(Function super A, ? extends B> f) {
return fmap(f);
}
/**
* Apply f
to each elements building a new list. This is the list functor.
*
* @see #map(Function)
*/
public abstract ListMonadic fmap(Function super A, ? extends B> f);
/** Alias for {@link #bind(Function)}. */
public final ListMonadic flatMap(Function super A, ? extends Iterable> f) {
return bind(f);
}
/**
* Monadic bind m a -> (a -> m b) -> m b
. Apply f
to each elements concatenating
* the results into a new list.
*/
public abstract ListMonadic bind(Function super A, ? extends Iterable> f);
/** Fold the list from left to right applying binary operator f
starting with zero
. */
public abstract B foldl(B zero, Function2 super B, ? super A, ? extends B> f);
/** Reduce the list from left to right applying binary operator f
. The list must not be empty. */
public abstract A reducel(Function2 super A, ? super A, ? extends A> f);
/** Append a
to the list. */
public abstract > ListMonadic concat(M m);
/** Construct a new list by prepending a
. */
public abstract ListMonadic cons(X a);
/** Retain all elements satisfying predicate p
. */
public abstract ListMonadic filter(Function super A, Boolean> p);
/** Return the first element satisfying predicate p
. */
public abstract Option find(Function super A, Boolean> p);
/** Check if at least one element satisfies predicate p
. */
public abstract boolean exists(Function super A, Boolean> p);
/** Apply side effect e
to each element. */
public abstract ListMonadic each(Function super A, Void> e);
/** Apply side effect e
to each element. Indexed version. */
public abstract ListMonadic eachIndex(Function2 super A, ? super Integer, Void> e);
public abstract > ListMonadic> zip(M bs);
public abstract ListMonadic> zip(B[] bs);
public abstract ListMonadic> zip(Iterator bs);
public abstract ListMonadic sort(Comparator c);
public abstract ListMonadic reverse();
/** Return the head of the list. */
public abstract Option headOpt();
/** Return the head of the list. */
public abstract A head();
/** Return the last element of the list. */
public abstract A last();
/** Return the last element of the list. */
public abstract Option lastOpt();
/** Turn the list into an option only if it contains exactly one element. */
public abstract Option option();
/** Return the tail of the list. */
public abstract ListMonadic tail();
/** Limit the list to the first n
elements. */
public abstract ListMonadic take(int n);
/** Drop the first n
elements of the list. */
public abstract ListMonadic drop(int n);
/** Process the wrapped list en bloc. */
public abstract ListMonadic inspect(Function super List, ? extends List> f);
/** Pattern matching on the wrapped list. */
public final B match(Matcher... ms) {
return match(list(ms));
}
/** Pattern matching on the wrapped list. */
public final B match(List> ms) {
for (Matcher m : ms) {
if (m.matches(this)) {
return m.apply(this);
}
}
return unexhaustiveMatch();
}
public abstract String mkString(String sep);
/** Return the wrapped, unmodifiable list. */
public abstract List value();
}
/** The iterator monad. */
public abstract static class IteratorMonadic implements Iterable {
private IteratorMonadic() {
}
/** Alias for {@link #fmap(Function)}. */
public final IteratorMonadic map(Function f) {
return fmap(f);
}
/** Apply f
to each element. */
public abstract IteratorMonadic fmap(Function f);
/** Apply f
to each element. The function also receives the element's index. */
public abstract IteratorMonadic mapIndex(Function2 f);
/** Alias for {@link #bind(Function)}. */
public final IteratorMonadic flatMap(Function> f) {
return bind(f);
}
/** Monadic bind. Apply f
to each elements concatenating the results. */
public abstract IteratorMonadic bind(Function> f);
// /**
// * Apply f
to each elements concatenating the results into a new list.
// */
// > IteratorMonadic flatMap(Function f);
/** Fold the elements applying binary operator f
starting with zero
. */
public abstract B fold(B zero, Function2 f);
/** Reduce the elements applying binary operator f
. The iterator must not be empty. */
public abstract A reduce(Function2 f);
// /**
// * Append a
to the list.
// */
// > ListMonadic concat(M a);
/** Retain all elements satisfying predicate p
. */
public abstract IteratorMonadic filter(Function p);
/** Check if at least one element satisfies predicate p
. */
public abstract boolean exists(Function p);
/** Limit iteration to the first n
elements. */
public abstract IteratorMonadic take(int n);
/** Apply side effect e
to each element. */
public abstract IteratorMonadic each(Function e);
/** Apply side effect e
to each element. Indexed version of {@link #each(Function)}. */
public abstract IteratorMonadic eachIndex(Function2 e);
/**
* Return the head of the iterator. ATTENTION: This method is not pure since it has the side effect of
* taking and wrapping the next element of the wrapped iterator.
*/
public abstract Option next();
/** Return the wrapped iterator. */
public abstract Iterator value();
/** Evaluate to a list. */
public abstract List eval();
}
private static List newListBuilder() {
return new ArrayList();
}
private static List newListBuilder(int size) {
return new ArrayList(size);
}
private static List newListBuilder(Collection as) {
return new ArrayList(as);
}
// -- matchers and constructors
public interface Matcher {
boolean matches(ListMonadic m);
B apply(ListMonadic m);
}
/** Matches the empty list. Like Haskell's []
*/
public static Matcher caseNil(final Function0 f) {
return new Matcher() {
@Override public boolean matches(ListMonadic m) {
return m.value().size() == 0;
}
@Override public B apply(ListMonadic m) {
return f.apply();
}
};
}
/** Matches lists with exactly one element. Like Haskell's (x:[])
*/
public static Matcher caseA(final Function f) {
return new Matcher() {
@Override public boolean matches(ListMonadic m) {
return m.value().size() == 1;
}
@Override public B apply(ListMonadic m) {
return f.apply(m.head());
}
};
}
/** Matches lists with at least one element. Like Haskell's (x:xs)
*/
public static Matcher caseAN(final Function2, B> f) {
return new Matcher() {
@Override public boolean matches(ListMonadic m) {
return m.value().size() >= 1;
}
@Override public B apply(ListMonadic m) {
return f.apply(m.head(), m.tail().value());
}
};
}
/** Matches any list. Like Haskell's (xs)
*/
public static Matcher caseN(final Function, B> f) {
return new Matcher() {
@Override public boolean matches(ListMonadic m) {
return true;
}
@Override public B apply(ListMonadic m) {
return f.apply(m.value());
}
};
}
// -- constructors
public static ListMonadic mlist(final Iterable as) {
return mlist(as.iterator());
}
/** Constructor for collections. */
public static ListMonadic mlist(final Collection as) {
return mlist(new ArrayList(as));
}
/** Constructor function optimized for lists. */
public static ListMonadic mlist(final List as) {
return new ListMonadic() {
@Override
public ListMonadic fmap(Function super A, ? extends B> f) {
final List target = newListBuilder(as.size());
for (A a : as) target.add(f.apply(a));
return mlist(target);
}
@Override
public ListMonadic bind(Function super A, ? extends Iterable> f) {
final List target = newListBuilder();
for (A a : as)
appendTo(target, f.apply(a));
return mlist(target);
}
@Override
public ListMonadic filter(Function super A, Boolean> p) {
final List target = newListBuilder(as.size());
for (A a : as) {
if (p.apply(a)) {
target.add(a);
}
}
return mlist(target);
}
@Override
public Option find(Function super A, Boolean> p) {
for (A a : as) {
if (p.apply(a))
return some(a);
}
return none();
}
@Override
public boolean exists(Function super A, Boolean> p) {
for (A a : as) {
if (p.apply(a))
return true;
}
return false;
}
@Override
public B foldl(B zero, Function2 super B, ? super A, ? extends B> f) {
B fold = zero;
for (A a : as) {
fold = f.apply(fold, a);
}
return fold;
}
@Override
public A reducel(Function2 super A, ? super A, ? extends A> f) {
if (as.size() == 0) {
throw new RuntimeException("Cannot reduce an empty list");
} else {
A fold = as.get(0);
for (int i = 1; i < as.size(); i++) {
fold = f.apply(fold, as.get(i));
}
return fold;
}
}
@Override
public Option headOpt() {
return !as.isEmpty() ? some(head()) : Option. none();
}
@Override
public A head() {
return as.get(0);
}
@Override
public A last() {
return as.get(as.size() - 1);
}
@Override
public Option lastOpt() {
return !as.isEmpty() ? some(last()) : Option. none();
}
@Override
public Option option() {
return as.size() == 1 ? some(as.get(0)) : Option. none();
}
@Override
public ListMonadic tail() {
if (as.size() <= 1)
return mlist();
return mlist(as.subList(1, as.size()));
}
@Override
public ListMonadic take(int n) {
return mlist(as.subList(0, min(as.size(), n)));
}
@Override
public ListMonadic drop(int n) {
return mlist(as.subList(min(as.size(), n), as.size()));
}
@Override
public > ListMonadic concat(M bs) {
return mlist(appendToM(Monadics. newListBuilder(), as, bs));
}
@Override
public ListMonadic cons(X a) {
return mlist(Collections. cons(a, as));
}
@Override
public ListMonadic inspect(Function super List, ? extends List> f) {
return mlist(f.apply(as));
}
@Override
public ListMonadic each(Function super A, Void> e) {
for (A a : as)
e.apply(a);
return this;
}
@Override
public ListMonadic eachIndex(Function2 super A, ? super Integer, Void> e) {
int i = 0;
for (A a : as)
e.apply(a, i++);
return this;
}
@Override
public > ListMonadic> zip(M m) {
final List> target = newListBuilder();
final Iterator asi = as.iterator();
final Iterator mi = m.iterator();
while (asi.hasNext() && mi.hasNext()) {
target.add(tuple(asi.next(), mi.next()));
}
return mlist(target);
}
@Override
public ListMonadic> zip(B[] bs) {
final List> target = newListBuilder(min(as.size(), bs.length));
int i = 0;
final Iterator asi = as.iterator();
while (asi.hasNext() && i < bs.length) {
target.add(tuple(asi.next(), bs[i++]));
}
return mlist(target);
}
@Override
public ListMonadic> zip(Iterator bs) {
final List> target = newListBuilder(as.size());
final Iterator asi = as.iterator();
while (asi.hasNext() && bs.hasNext()) {
target.add(tuple(asi.next(), bs.next()));
}
return mlist(target);
}
@Override
public ListMonadic sort(Comparator c) {
final List target = newListBuilder(as.size());
target.addAll(as);
java.util.Collections.sort(target, c);
return mlist(target);
}
@Override
public ListMonadic reverse() {
final List target = newListBuilder(as);
java.util.Collections.reverse(target);
return mlist(target);
}
@Override
public String mkString(String sep) {
return Collections.mkString(as, sep);
}
@Override
public Iterator iterator() {
return as.iterator();
}
@Override
public List value() {
return java.util.Collections.unmodifiableList(as);
}
};
}
/** Constructor function optimized for arrays. */
public static ListMonadic mlist(final A... as) {
return new ListMonadic() {
@Override
public ListMonadic fmap(Function super A, ? extends B> f) {
final List target = newListBuilder(as.length);
for (A a : as)
target.add(f.apply(a));
return mlist(target);
}
@Override
public ListMonadic bind(Function super A, ? extends Iterable> f) {
final List target = newListBuilder();
for (A a : as)
appendTo(target, f.apply(a));
return mlist(target);
}
@Override
public ListMonadic filter(Function super A, Boolean> p) {
List target = newListBuilder(as.length);
for (A a : as) {
if (p.apply(a)) {
target.add(a);
}
}
return mlist(target);
}
@Override
public Option find(Function super A, Boolean> p) {
for (A a : as) {
if (p.apply(a))
return some(a);
}
return none();
}
@Override
public boolean exists(Function super A, Boolean> p) {
for (A a : as) {
if (p.apply(a))
return true;
}
return false;
}
@Override
public B foldl(B zero, Function2 super B, ? super A, ? extends B> f) {
B fold = zero;
for (A a : as) {
fold = f.apply(fold, a);
}
return fold;
}
@Override
public A reducel(Function2 super A, ? super A, ? extends A> f) {
if (as.length == 0) {
throw new RuntimeException("Cannot reduce an empty list");
} else {
A fold = as[0];
for (int i = 1; i < as.length; i++) {
fold = f.apply(fold, as[i]);
}
return fold;
}
}
@Override
public Option headOpt() {
return as.length != 0 ? some(as[0]) : Option. none();
}
@Override
public A head() {
return as[0];
}
@Override
public A last() {
return as[as.length - 1];
}
@Override
public Option lastOpt() {
return as.length > 0 ? some(last()) : Option. none();
}
@Override
public Option option() {
return as.length == 1 ? some(as[0]) : Option. none();
}
@Override
public ListMonadic tail() {
if (as.length <= 1)
return mlist();
return (ListMonadic) mlist(ArrayUtils.subarray(as, 1, as.length));
}
@Override
public ListMonadic take(int n) {
return (ListMonadic) mlist(ArrayUtils.subarray(as, 0, n));
}
@Override
public ListMonadic drop(int n) {
return (ListMonadic) mlist(ArrayUtils.subarray(as, n, as.length));
}
@Override
public > ListMonadic concat(M bs) {
final List t = newListBuilder(as.length);
return mlist(appendTo(appendToA(t, as), bs));
}
@Override
public ListMonadic cons(X a) {
return mlist(Collections. concat(Collections. list(a), Collections. list(as)));
}
@Override
public ListMonadic inspect(Function super List, ? extends List> f) {
return mlist(f.apply(value()));
}
@Override
public ListMonadic each(Function super A, Void> e) {
for (A a : as) {
e.apply(a);
}
return mlist(as);
}
@Override
public ListMonadic eachIndex(Function2 super A, ? super Integer, Void> e) {
int i = 0;
for (A a : as) {
e.apply(a, i++);
}
return this;
}
@Override
public > ListMonadic> zip(M m) {
final List> target = newListBuilder();
int i = 0;
final Iterator mi = m.iterator();
while (i < as.length && mi.hasNext()) {
target.add(tuple(as[i++], mi.next()));
}
return mlist(target);
}
@Override
public ListMonadic> zip(B[] bs) {
final List> target = newListBuilder(min(as.length, bs.length));
int i = 0;
while (i < as.length && i < bs.length) {
target.add(tuple(as[i], bs[i]));
i++;
}
return mlist(target);
}
@Override
public ListMonadic> zip(Iterator bs) {
final List> target = newListBuilder(as.length);
int i = 0;
while (i < as.length && bs.hasNext()) {
target.add(tuple(as[i++], bs.next()));
}
return mlist(target);
}
@Override
public ListMonadic sort(Comparator c) {
final List target = list(as);
java.util.Collections.sort(target, c);
return mlist(target);
}
@Override
public ListMonadic reverse() {
final List target = list(as);
java.util.Collections.reverse(target);
return mlist(target);
}
@Override
public String mkString(String sep) {
return Arrays.mkString(as, sep);
}
@Override
public Iterator iterator() {
return Collections.iterator(as);
}
@Override
public List value() {
return asList(as);
}
};
}
/** Constructor function optimized for iterators. */
public static ListMonadic mlist(final Iterator as) {
return new ListMonadic() {
@Override
public ListMonadic fmap(Function super A, ? extends B> f) {
final List target = newListBuilder();
while (as.hasNext()) {
target.add(f.apply(as.next()));
}
return mlist(target);
}
@Override
public ListMonadic bind(Function super A, ? extends Iterable> f) {
final List target = newListBuilder();
while (as.hasNext())
appendTo(target, f.apply(as.next()));
return mlist(target);
}
@Override
public ListMonadic filter(Function super A, Boolean> p) {
final List target = newListBuilder();
while (as.hasNext()) {
A a = as.next();
if (p.apply(a)) {
target.add(a);
}
}
return mlist(target);
}
@Override
public Option find(Function super A, Boolean> p) {
for (A a : forc(as)) {
if (p.apply(a))
return some(a);
}
return none();
}
@Override
public boolean exists(Function super A, Boolean> p) {
for (A a : forc(as)) {
if (p.apply(a))
return true;
}
return false;
}
@Override
public B foldl(B zero, Function2 super B, ? super A, ? extends B> f) {
B fold = zero;
while (as.hasNext()) {
fold = f.apply(fold, as.next());
}
return fold;
}
@Override
public A reducel(Function2 super A, ? super A, ? extends A> f) {
if (!as.hasNext()) {
throw new RuntimeException("Cannot reduce an empty iterator");
} else {
A fold = as.next();
while (as.hasNext()) {
fold = f.apply(fold, as.next());
}
return fold;
}
}
@Override
public Option headOpt() {
throw new UnsupportedOperationException();
}
@Override
public A head() {
throw new UnsupportedOperationException();
}
@Override
public A last() {
throw new UnsupportedOperationException();
}
@Override
public Option lastOpt() {
throw new UnsupportedOperationException();
}
@Override
public Option option() {
throw new UnsupportedOperationException();
}
@Override
public ListMonadic tail() {
throw new UnsupportedOperationException();
}
@Override
public ListMonadic take(final int n) {
return mlist(new Iter() {
private int count = 0;
@Override
public boolean hasNext() {
return count < n && as.hasNext();
}
@Override
public A next() {
if (count < n) {
count++;
return as.next();
} else {
throw new NoSuchElementException();
}
}
});
}
@Override
public ListMonadic drop(int n) {
int count = n;
while (as.hasNext() && count > 0) {
as.next();
count--;
}
return mlist(as);
}
@Override
public > ListMonadic concat(M bs) {
throw new UnsupportedOperationException();
}
@Override
public ListMonadic cons(X a) {
return null; // todo
}
@Override
public ListMonadic inspect(Function super List, ? extends List> f) {
throw new UnsupportedOperationException();
}
@Override
public ListMonadic each(Function super A, Void> e) {
while (as.hasNext()) e.apply(as.next());
return this;
}
@Override
public ListMonadic eachIndex(Function2 super A, ? super Integer, Void> e) {
int i = 0;
while (as.hasNext())
e.apply(as.next(), i++);
return this;
}
@Override
public > ListMonadic> zip(M m) {
final List> target = newListBuilder();
final Iterator mi = m.iterator();
while (as.hasNext() && mi.hasNext()) {
target.add(tuple(as.next(), mi.next()));
}
return mlist(target);
}
@Override
public ListMonadic> zip(B[] bs) {
final List> target = newListBuilder(bs.length);
int i = 0;
while (as.hasNext() && i < bs.length) {
target.add(tuple(as.next(), bs[i++]));
}
return mlist(target);
}
@Override
public ListMonadic> zip(Iterator bs) {
final List> target = newListBuilder();
while (as.hasNext() && bs.hasNext()) {
target.add(tuple(as.next(), bs.next()));
}
return mlist(target);
}
@Override
public Iterator iterator() {
return as;
}
@Override
public ListMonadic sort(Comparator c) {
throw new UnsupportedOperationException();
}
@Override
public ListMonadic reverse() {
throw new UnsupportedOperationException();
}
@Override
public String mkString(String sep) {
return Collections.mkString(toList(as), sep);
}
@Override
public List value() {
return java.util.Collections.unmodifiableList(toList(as));
}
};
}
/** Constructor function optimized for iterators. */
public static IteratorMonadic mlazy(final Iterator as) {
return new IteratorMonadic() {
@Override
public IteratorMonadic fmap(final Function f) {
return mlazy(new Iter() {
@Override
public boolean hasNext() {
return as.hasNext();
}
@Override
public B next() {
return f.apply(as.next());
}
});
}
@Override
public IteratorMonadic mapIndex(final Function2 f) {
return mlazy(new Iter() {
private int i = 0;
@Override
public boolean hasNext() {
return as.hasNext();
}
@Override
public B next() {
return f.apply(as.next(), i++);
}
});
}
@Override
public IteratorMonadic bind(final Function> f) {
return mlazy(new Iter() {
@Override
public boolean hasNext() {
return step.hasNext() || step().hasNext();
}
@Override
public B next() {
if (step.hasNext()) {
return step.next();
} else {
return step().next();
}
}
// iterator state management
private Iterator step = Monadics.emptyIter();
private Iterator step() {
while (!step.hasNext() && as.hasNext()) {
step = f.apply(as.next());
}
return step;
}
});
}
@Override
public boolean exists(Function p) {
for (A a : forc(as)) {
if (p.apply(a))
return true;
}
return false;
}
@Override
public IteratorMonadic filter(final Function p) {
return mlazy(new Iter() {
private A next = null;
@Override
public boolean hasNext() {
if (next != null) {
return true;
} else {
for (A a : forc(as)) {
if (p.apply(a)) {
next = a;
return true;
}
}
return false;
}
}
@Override
public A next() {
try {
if (next != null || hasNext()) {
return next;
} else {
throw new NoSuchElementException();
}
} finally {
next = null;
}
}
});
}
@Override
public B fold(B zero, Function2 f) {
throw new UnsupportedOperationException();
}
@Override
public A reduce(Function2 f) {
throw new UnsupportedOperationException();
}
@Override
public IteratorMonadic take(final int n) {
return mlazy(new Iter() {
private int count = 0;
@Override
public boolean hasNext() {
return count < n && as.hasNext();
}
@Override
public A next() {
if (count < n) {
count++;
return as.next();
} else {
throw new NoSuchElementException();
}
}
});
}
@Override
public IteratorMonadic each(final Function e) {
return mlazy(new Iter() {
@Override
public boolean hasNext() {
return as.hasNext();
}
@Override
public A next() {
final A a = as.next();
e.apply(a);
return a;
}
});
}
@Override
public IteratorMonadic eachIndex(final Function2 e) {
return mlazy(new Iter() {
private int i = 0;
@Override
public boolean hasNext() {
return as.hasNext();
}
@Override
public A next() {
final A a = as.next();
e.apply(a, i++);
return a;
}
});
}
@Override
public Option next() {
return as.hasNext() ? some(as.next()) : Option.none();
}
@Override
public Iterator iterator() {
return as;
}
@Override
public Iterator value() {
return as;
}
@Override
public List eval() {
return toList(as);
}
};
}
/** Constructor function optimized for lists. */
public static IteratorMonadic mlazy(final List as) {
return mlazy(as.iterator());
}
/** Constructor function. */
public static IteratorMonadic mlazy(final Iterable as) {
return mlazy(as.iterator());
}
/** Constructor function optimized for arrays. */
public static IteratorMonadic mlazy(A... as) {
return mlazy(iterator(as));
}
private abstract static class Iter implements Iterator {
@Override
public final void remove() {
throw new UnsupportedOperationException();
}
}
private static Iterator emptyIter() {
return new Iter() {
@Override
public boolean hasNext() {
return false;
}
@Override
public A next() {
throw new NoSuchElementException();
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy