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
32 changes: 31 additions & 1 deletion core/src/main/java/fj/P2.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package fj;

import static fj.Function.*;

import static fj.data.optic.PLens.pLens;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about putting optic instances in the same classes. The reason is that it will cause cyclic references between packages/classes (e.g. PLens depends on List and List depends on PLens). In Monocle there is no such problem: optic instances are located in monocle.std package. What if we use the same strategy and create fj.data.optic.std package?

Also, what do you think about creating a new artifact functionaljava-lens with all fj.data.optic stuff in it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a valid concern. However there is already many cyclic packages references in fj (eg. Monoid<->Stream). Scalaz solve cyclic references by putting all in a single package. For those reasons I considered all of fj to be a single 'package'.
I can move optic instances into their own package/artifact, the downside I can see is that it will be harder to find them. pro is that it will address @mperry concern about having a clear distinction for optics 'factory methods'.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Monoid and Stream are central entities in functional programming, while lenses are not (IMO). Lenses are great, but I think optic methods in "standard" classes will confuse people that are new to functional programming.
Regarding Scalaz: putting everything into a single package led to a huge jar (10 MB) which is not good at all. I would like not to repeat this mistake in functionaljava.
Anyway, this is just my personal point view, so I would like to hear other opinions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lenses (optics) are way more important to functional programming, in a practical regard.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to see them in their own package, but don't see the need for a separate artifact.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another reason is that instances for List regarding things like Equal, Show, etc. are in those classes, rather than List.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mperry so... do you suggest to put optics instances in Prism, Lens, Ptraversal and the like...? I think this could be practical...
if they are isolated in their own package, those 'orphan' optics that will be harder to find which will lower their practicality.

import fj.data.*;
import fj.data.optic.Lens;
import fj.data.optic.PLens;

/**
* A product-2.
Expand Down Expand Up @@ -342,6 +344,34 @@ public static <A, B, C> F2<A, B, C> untuple(final F<P2<A, B>, C> f) {
return (a, b) -> f.f(P.p(a, b));
}

/**
* Polyomorphic lens targeted on _1.
*/
public static <A, B, C> PLens<P2<A, B>, P2<C, B>, A, C> _1pLens() {
return pLens(__1(), a -> p2 -> P.p(a, p2._2()));
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A dumb question: Where does the naming convention for _1pLens come from?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no real convention here, I'm happy to change the name. I originally wanted _1Lens to be named __1 but that method already exist.
_1pLens being the polymorphic version of _1Lens.
Or should we use ___1 and ___2 for the lenses? not sure what would be the name for polymorphic version then...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for reference, the Monocle library use first and second for the lenses on scala tuple:
https://github.com/julien-truffaut/Monocle/blob/master/core/src/main/scala/monocle/std/Tuple2.scala

/**
* Monomorphic lens targeted on _1.
*/
public static <A, B, C> Lens<P2<A, B>, A> _1Lens() {
return new Lens<>(_1pLens());
}

/**
* Polyomorphic lens targeted on _2.
*/
public static <A, B, C> PLens<P2<A, B>, P2<A, C>, B, C> _2pLens() {
return pLens(__2(), b -> p2 -> P.p(p2._1(), b));
}

/**
* Monomorphic lens targeted on _1.
*/
public static <A, B, C> Lens<P2<A, B>, B> _2Lens() {
return new Lens<>(_2pLens());
}

@Override
public String toString() {
return Show.p2Show(Show.<A>anyShow(), Show.<B>anyShow()).showS(this);
Expand Down
152 changes: 152 additions & 0 deletions core/src/main/java/fj/data/List.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@
import static fj.data.List.Buffer.*;
import static fj.data.Option.none;
import static fj.data.Option.some;
import static fj.data.optic.Optional.optional;
import static fj.data.optic.Prism.prism;
import static fj.data.vector.V.v;
import static fj.function.Booleans.not;
import static fj.Ordering.GT;
import static fj.Ord.intOrd;
import fj.Ordering;
import fj.control.Trampoline;
import fj.control.parallel.Promise;
import fj.control.parallel.Strategy;
import fj.data.optic.Optional;
import fj.data.optic.PTraversal;
import fj.data.optic.Prism;
import fj.data.optic.Traversal;
import fj.data.vector.V2;
import fj.function.Effect1;

import java.util.AbstractCollection;
Expand Down Expand Up @@ -608,6 +618,119 @@ public <B> IO<List<B>> traverseIO(F<A, IO<B>> f) {
);
}

public <C, B> F<C, List<B>> traverseF(F<A, F<C, B>> f) {
return this.foldRight(
(a, acc) -> Function.bind(acc,
(bs) -> Function.<C, B, List<B>> compose(b -> bs.cons(b), f.f(a))),
Function.constant(List.<B> nil())
);
}

public <B> Trampoline<List<B>> traverseTrampoline(final F<A, Trampoline<B>> f) {
return foldRight(
(a, acc) -> f.f(a).bind(b -> acc.map(bs -> bs.cons(b))),
Trampoline.pure(List.<B> nil()));
}

public <B> Promise<List<B>> traversePromise(final F<A, Promise<B>> f) {
return foldRight(
(a, acc) -> f.f(a).bind(b -> acc.fmap(bs -> bs.cons(b))),
Promise.promise(Strategy.idStrategy(), p(List.<B> nil())));
}

public <B> List<List<B>> traverseList(final F<A, List<B>> f) {
return foldRight(
(a, acc) -> f.f(a).bind(b -> acc.map(bs -> bs.cons(b))),
List.single(List.<B> nil()));
}

public <E, B> Validation<E, List<B>> traverseValidation(final F<A, Validation<E, B>> f) {
return foldRight(
(a, acc) -> f.f(a).bind(b -> acc.map(bs -> bs.cons(b))),
Validation.success(List.<B> nil()));
}

public <B> V2<List<B>> traverseV2(final F<A, V2<B>> f) {
return foldRight(
(a, acc) -> acc.apply(f.f(a).<F<List<B>, List<B>>> map(e -> es -> es.cons(e))),
v(List.<B> nil(), List.<B> nil()));
}

/**
* polymorphic traversal
*/
public static <A, B> PTraversal<List<A>, List<B>, A, B> _pTraversal() {
return new PTraversal<List<A>, List<B>, A, B>() {

@Override
public <C> F<List<A>, F<C, List<B>>> modifyFunctionF(F<A, F<C, B>> f) {
return l -> l.traverseF(f);
}

@Override
public <L> F<List<A>, Either<L, List<B>>> modifyEitherF(F<A, Either<L, B>> f) {
return l -> l.traverseEither(f);
}

@Override
public F<List<A>, IO<List<B>>> modifyIOF(F<A, IO<B>> f) {
return l -> l.traverseIO(f);
}

@Override
public F<List<A>, Trampoline<List<B>>> modifyTrampolineF(F<A, Trampoline<B>> f) {
return l -> l.traverseTrampoline(f);
}

@Override
public F<List<A>, Promise<List<B>>> modifyPromiseF(F<A, Promise<B>> f) {
return l -> l.traversePromise(f);
}

@Override
public F<List<A>, List<List<B>>> modifyListF(F<A, List<B>> f) {
return l -> l.traverseList(f);
}

@Override
public F<List<A>, Option<List<B>>> modifyOptionF(F<A, Option<B>> f) {
return l -> l.traverseOption(f);
}

@Override
public F<List<A>, Stream<List<B>>> modifyStreamF(F<A, Stream<B>> f) {
return l -> l.traverseStream(f);
}

@Override
public F<List<A>, P1<List<B>>> modifyP1F(F<A, P1<B>> f) {
return l -> l.traverseP1(f);
}

@Override
public <E> F<List<A>, Validation<E, List<B>>> modifyValidationF(F<A, Validation<E, B>> f) {
return l -> l.traverseValidation(f);
}

@Override
public F<List<A>, V2<List<B>>> modifyV2F(F<A, V2<B>> f) {
return l -> l.traverseV2(f);
}

@Override
public <M> F<List<A>, M> foldMap(Monoid<M> monoid, F<A, M> f) {
return l -> monoid.sumLeft(l.map(f));
}
};
}

/**
* monomorphic traversal
*/
public static <A> Traversal<List<A>, A> _traversal() {
return new Traversal<>(_pTraversal());
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer lensTraversal to _traversal for this method. I don't like relying on using underscore to indicate the returned value is a lens. Take _head vs lensHead for example.

However, take note that I am a beginner user of lens.

This would mean the P2._1Lens would be renamed to lens_1. The polymorphic version could be lensp_1 or plens_1.

Your thoughts?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lens is one kind of optics, Traversal is another, so lensTraversal would be confusing. so for _traversal I see only List.listTraversal or simply List.traversal as alternative naming.
Maybe @tonymorris could suggest a good naming convention for optics instances in functionaljava... :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I was using lens as the package as a whole, like the Haskell lens package (e.g. Control.Lens.Iso).

For others learning about this information, there is lots of useful info on the Github lens library page, https://github.com/ekmett/lens.

/**
* Performs function application within a list (applicative functor pattern).
*
Expand Down Expand Up @@ -1054,6 +1177,13 @@ public final List<A> nub(final Ord<A> o) {
return sort(o).group(o.equal()).map(List.<A>head_());
}

/**
* Optional targeted on Cons head.
*/
public static <A> Optional<List<A>, A> _head() {
return optional(l -> l.toOption(), a -> l -> l.<List<A>> list(l, constant(cons_(a))));
}

/**
* First-class head function.
*
Expand All @@ -1063,6 +1193,14 @@ public static <A> F<List<A>, A> head_() {
return list -> list.head();
}

/**
* Optional targeted on Cons tail.
*/
public static <A> Optional<List<A>, List<A>> _tail() {
return optional(l -> l.<Option<List<A>>> list(none(), h -> tail -> some(tail)),
tail -> l -> l.list(l, h -> constant(cons(h, tail))));
}

/**
* First-class tail function.
*
Expand Down Expand Up @@ -1434,6 +1572,13 @@ public static <A> List<A> nil() {
return (Nil<A>) Nil.INSTANCE;
}

/**
* Nil prism
*/
public static <A> Prism<List<A>, Unit> _nil() {
return prism(l -> l.isEmpty() ? some(unit()) : none(), constant(nil()));
}

/**
* Returns a function that prepends (cons) an element to a list to produce a new list.
*
Expand All @@ -1443,6 +1588,13 @@ public static <A> F<A, F<List<A>, List<A>>> cons() {
return a -> tail -> cons(a, tail);
}

/**
* Cons prism
*/
public static <A> Prism<List<A>, P2<A, List<A>>> _cons() {
return prism(l -> l.<Option<P2<A, List<A>>>> list(none(), h -> tail -> some(P.p(h, tail))), c -> cons(c._1(), c._2()));
}

public static <A> F2<A, List<A>, List<A>> cons_() {
return (a, listA) -> cons(a, listA);
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/fj/data/optic/Fold.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public final F<S, Option<A>> find(final F<A, Boolean> p) {

/** get the first target of a {@link Fold} */
public final Option<A> headOption(final S s) {
return find(__ -> true).f(s);
return find(Function.constant(Boolean.TRUE)).f(s);
}

/** check if at least one target satisfies the predicate */
Expand Down
120 changes: 117 additions & 3 deletions core/src/main/java/fj/data/optic/Optional.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package fj.data.optic;

import fj.F;
import fj.Function;
import fj.P;
import fj.P1;
import fj.P2;
import fj.control.Trampoline;
import fj.control.parallel.Promise;
import fj.control.parallel.Strategy;
import fj.data.Either;
import fj.data.IO;
import fj.data.IOFunctions;
import fj.data.List;
import fj.data.Option;
import fj.data.Stream;
import fj.data.Validation;
import fj.data.vector.V;
import fj.data.vector.V2;

/** {@link POptional} restricted to monomorphic update */
Expand Down Expand Up @@ -166,9 +171,118 @@ public static <S> Optional<S, S> id() {
return new Optional<>(POptional.pId());
}

/** create a {@link Optional} using the canonical functions: getOrModify and set */
public static final <S, A> Optional<S, A> optional(final F<S, Either<S, A>> getOrModify, final F<A, F<S, S>> set) {
return new Optional<>(POptional.pOptional(getOrModify, set));
public static final <S, A> Optional<S, A> optional(final F<S, Option<A>> getOption, final F<A, F<S, S>> set) {
return new Optional<>(new POptional<S, S, A, A>() {

@Override
public Either<S, A> getOrModify(final S s) {
return getOption.f(s).option(Either.left(s), Either.<S, A> right_());
}

@Override
public F<S, S> set(final A a) {
return set.f(a);
}

@Override
public Option<A> getOption(final S s) {
return getOption.f(s);
}

@Override
public <C> F<S, F<C, S>> modifyFunctionF(final F<A, F<C, A>> f) {
return s -> getOption.f(s).<F<C, S>> option(
(C __) -> s,
a -> Function.compose(b -> set.f(b).f(s), f.f(a))
);
}

@Override
public <L> F<S, Either<L, S>> modifyEitherF(final F<A, Either<L, A>> f) {
return s -> getOption.f(s).<Either<L, S>> option(
Either.right(s),
t -> f.f(t).right().map(b -> set.f(b).f(s))
);
}

@Override
public F<S, IO<S>> modifyIOF(final F<A, IO<A>> f) {
return s -> getOption.f(s).option(
IOFunctions.unit(s),
a -> IOFunctions.<A, S> map(f.f(a), b -> set.f(b).f(s))
);
}

@Override
public F<S, Trampoline<S>> modifyTrampolineF(final F<A, Trampoline<A>> f) {
return s -> getOption.f(s).option(
Trampoline.pure(s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public F<S, Promise<S>> modifyPromiseF(final F<A, Promise<A>> f) {
return s -> getOption.f(s).<Promise<S>> option(
() -> Promise.promise(Strategy.idStrategy(), P.p(s)),
t -> f.f(t).fmap(b -> set.f(b).f(s))
);
}

@Override
public F<S, List<S>> modifyListF(final F<A, List<A>> f) {
return s -> getOption.f(s).<List<S>> option(
() -> List.single(s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public F<S, Option<S>> modifyOptionF(final F<A, Option<A>> f) {
return s -> getOption.f(s).option(
Option.some(s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public F<S, Stream<S>> modifyStreamF(final F<A, Stream<A>> f) {
return s -> getOption.f(s).<Stream<S>> option(
() -> Stream.single(s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public F<S, P1<S>> modifyP1F(final F<A, P1<A>> f) {
return s -> getOption.f(s).<P1<S>> option(
P.p(s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public <E> F<S, Validation<E, S>> modifyValidationF(final F<A, Validation<E, A>> f) {
return s -> getOption.f(s).<Validation<E, S>> option(
() -> Validation.<E, S> success(s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public F<S, V2<S>> modifyV2F(final F<A, V2<A>> f) {
return s -> getOption.f(s).<V2<S>> option(
() -> V.v(s, s),
t -> f.f(t).map(b -> set.f(b).f(s))
);
}

@Override
public F<S, S> modify(final F<A, A> f) {
return s -> getOption.f(s).option(s, a -> set.f(f.f(a)).f(s));
}

});
}

}
Loading