All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.util.data.Monadics Maven / Gradle / Ivy

There is a newer version: 16.7
Show newest version
/**
 * 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.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.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 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 f);

    /** Alias for {@link #bind(Function)}. */
    public final  ListMonadic flatMap(Function> 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> f);

    /** Fold the list from left to right applying binary operator f starting with zero. */
    public abstract  B foldl(B zero, Function2 f);

    /** Reduce the list from left to right applying binary operator f. The list must not be empty. */
    public abstract A reducel(Function2 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 p);

    /** Return the first element satisfying predicate p. */
    public abstract Option find(Function p);

    /** Check if at least one element satisfies predicate p. */
    public abstract boolean exists(Function p);

    /** Apply side effect e to each element. */
    public abstract ListMonadic each(Function e);

    public abstract > ListMonadic> zip(M bs);

    public abstract ListMonadic sort(Comparator c);

    /** Return the head of the list. */
    public abstract Option headOpt();

    /** 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);

    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);

    // /**
    // * 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 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);
  }

  // -- matchers and constructors

  // -- constructors

  /** 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 f) {
        final List target = newListBuilder(as.size());
        for (A a : as) target.add(f.apply(a));
        return mlist(target);
      }

      @Override
      public  ListMonadic bind(Function> f) {
        final List target = newListBuilder();
        for (A a : as)
          appendTo(target, f.apply(a));
        return mlist(target);
      }

      @Override
      public ListMonadic filter(Function 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 p) {
        for (A a : as) {
          if (p.apply(a))
            return some(a);
        }
        return none();
      }

      @Override
      public boolean exists(Function p) {
        for (A a : as) {
          if (p.apply(a))
            return true;
        }
        return false;
      }

      @Override
      public  B foldl(B zero, Function2 f) {
        B fold = zero;
        for (A a : as) {
          fold = f.apply(fold, a);
        }
        return fold;
      }

      @Override
      public A reducel(Function2 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();
      }

      private A head() {
        return as.get(0);
      }

      @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 each(Function e) {
        for (A a : as)
          e.apply(a);
        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 sort(Comparator c) {
        final List target = newListBuilder(as.size());
        target.addAll(as);
        java.util.Collections.sort(target, c);
        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 f) {
        final List target = newListBuilder(as.length);
        for (A a : as)
          target.add(f.apply(a));
        return mlist(target);
      }

      @Override
      public  ListMonadic bind(Function> f) {
        final List target = newListBuilder();
        for (A a : as)
          appendTo(target, f.apply(a));
        return mlist(target);
      }

      @Override
      public ListMonadic filter(Function 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 p) {
        for (A a : as) {
          if (p.apply(a))
            return some(a);
        }
        return none();
      }

      @Override
      public boolean exists(Function p) {
        for (A a : as) {
          if (p.apply(a))
            return true;
        }
        return false;
      }

      @Override
      public  B foldl(B zero, Function2 f) {
        B fold = zero;
        for (A a : as) {
          fold = f.apply(fold, a);
        }
        return fold;
      }

      @Override
      public A reducel(Function2 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 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 each(Function e) {
        for (A a : as) {
          e.apply(a);
        }
        return mlist(as);
      }

      @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 sort(Comparator c) {
        final List target = list(as);
        java.util.Collections.sort(target, c);
        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 f) {
        final List target = newListBuilder();
        while (as.hasNext()) {
          target.add(f.apply(as.next()));
        }
        return mlist(target);
      }

      @Override
      public  ListMonadic bind(Function> f) {
        final List target = newListBuilder();
        while (as.hasNext())
          appendTo(target, f.apply(as.next()));
        return mlist(target);
      }

      @Override
      public ListMonadic filter(Function 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 p) {
        for (A a : forc(as)) {
          if (p.apply(a))
            return some(a);
        }
        return none();
      }

      @Override
      public boolean exists(Function p) {
        for (A a : forc(as)) {
          if (p.apply(a))
            return true;
        }
        return false;
      }

      @Override
      public  B foldl(B zero, Function2 f) {
        B fold = zero;
        while (as.hasNext()) {
          fold = f.apply(fold, as.next());
        }
        return fold;
      }

      @Override
      public A reducel(Function2 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 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 each(Function e) {
        while (as.hasNext()) e.apply(as.next());
        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 Iterator iterator() {
        return as;
      }

      @Override
      public ListMonadic sort(Comparator c) {
        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 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 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());
  }

  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();
      }
    };
  }
}