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

com.github.tonivade.purefun.free.Cofree Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-2020, Antonio Gabriel Muñoz Conejo 
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.purefun.free;

import com.github.tonivade.purefun.Function1;
import com.github.tonivade.purefun.Function2;
import com.github.tonivade.purefun.Higher1;
import com.github.tonivade.purefun.HigherKind;
import com.github.tonivade.purefun.Kind;
import com.github.tonivade.purefun.Operator2;
import com.github.tonivade.purefun.type.Eval;
import com.github.tonivade.purefun.typeclasses.Applicative;
import com.github.tonivade.purefun.typeclasses.Functor;
import com.github.tonivade.purefun.typeclasses.Monoid;
import com.github.tonivade.purefun.typeclasses.Traverse;

import static java.util.Objects.requireNonNull;

@HigherKind
public final class Cofree {

  private final Functor functor;
  private final A head;
  private final Eval>> tail;

  private Cofree(Functor functor, A head, Eval>> tail) {
    this.functor = requireNonNull(functor);
    this.head = requireNonNull(head);
    this.tail = requireNonNull(tail);
  }

  public A extract() {
    return head;
  }

  public Higher1> tailForced() {
    return tail.value();
  }

  public Cofree runTail() {
    return of(functor, head, Eval.now(tail.value()));
  }

  public Cofree run() {
    return of(functor, head, Eval.now(transformTail(Cofree::run).value()));
  }

  public  Cofree map(Function1 mapper) {
    return transform(mapper, c -> c.map(mapper));
  }

  public  Cofree coflatMap(Function1, B> mapper) {
    return of(functor, mapper.apply(this), transformTail(c -> c.coflatMap(mapper)));
  }

  // XXX: remove eval applicative instance parameter, if instances project is added then cyclic dependency problem
  public  Eval fold(Applicative applicative, Traverse traverse, Function2, Eval> mapper) {
    Eval> eval =
        traverse.traverse(applicative, tailForced(), c -> c.fold(applicative, traverse, mapper).kind1())
            .fix1(Eval::narrowK);
    return eval.flatMap(fb -> mapper.apply(extract(), fb));
  }

  public  Eval reduce(Applicative applicative, Traverse traverse,
                            Function1 initial, Operator2 combine) {
    return fold(applicative, traverse,
        (a, fb) -> Eval.later(() -> traverse.fold(Monoid.of(initial.apply(a), combine), fb)));
  }

  public Eval reduceToString(Applicative applicative, Traverse traverse, Operator2 join) {
    return reduce(applicative, traverse, String::valueOf, join);
  }

  public  Cofree transform(Function1 headMap, Function1, Cofree> tailMap) {
    return of(functor, transformHead(headMap), transformTail(tailMap));
  }

  private  B transformHead(Function1 headMap) {
    return headMap.apply(head);
  }

  private  Eval>> transformTail(Function1, Cofree> tailMap) {
    return tail.map(t -> functor.map(t, tailMap));
  }

  public static  Cofree unfold(Functor functor, A head, Function1> unfold) {
    return of(functor, head, Eval.later(() -> functor.map(unfold.apply(head), a -> unfold(functor, a, unfold))));
  }

  public static  Cofree of(Functor functor, A head, Eval>> tail) {
    return new Cofree<>(functor, head, tail);
  }
}