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

Breaking changes in 3.0.0 #1493

Closed
23 tasks done
danieldietrich opened this issue Aug 15, 2016 · 13 comments
Closed
23 tasks done

Breaking changes in 3.0.0 #1493

danieldietrich opened this issue Aug 15, 2016 · 13 comments

Comments

@danieldietrich
Copy link
Contributor

danieldietrich commented Aug 15, 2016

Breaking Changes

This is just a List of ideas. We will create separate PRs (and maybe also issues).

Remove unnecessary ballast

  • Check if we can remove Either's Left/RightProjection now that Either is right-biased. See also Elm's Result type (similar to Either) - yes, we can - issue created: Remove Either projections in favor of swap #2071
  • discontinue javaslang-pure~~
  • remove javaslang.Kind1, Kind2
  • remove Option.nothing()
  • consider making λ.Memoized package-private/internal. See Open Function*.memoized() for extension #1905
  • remove Promise (see Add factory method that allows to resolve/reject a Future #1529) - (update: it will most probably stay for we will have new Future API for promises)
  • rethink or remove run() for Match (see Add Match.run(cases) syntax #1216, Wish for API.run((r) -> {}) method #1218) - I don't want to put too much additional effort into the Match API because Java will have native pattern matching in near future.
  • Case API is ambiguous. We should allow only Function (not Supplier or value T) in order to calculate result
  • Value.toOption() and Value.getOption() are redundant. Remove one? (However, getOption needs to return this if this instanceof Option)
  • Widen Validation's List<E> to List<? extends E> in many cases. - moved to Add more syntactic sugar to Validation #1565
  • We should consider to change Validation<List<E>, T> to Validation<Seq<E>, T> and internally use Vector instead of List.
  • Make AbstractIterator package-private. remove AbstractIterator and do hasNext()-checks manually in next(). (see this comment). Problem Iterator is an interface and we need to override toString(). Possible solution: make Iterator an abstract class. Better solution: Keep it as interface and do not provide toString() for Iterator - instead use hasNext() to check if it is empty.

Other breaking changes

  • provide first char upper case static factory methods (see #1431)
  • indexing collections with int instead of long
  • consider renaming CheckedFunction0..8 to ThrowingFunction0..8 (one more think to not think about from the user perspective - which one throws, the checked or the unchecked function?)
  • bring (creational method names of, ofAll) and (conversion names fromXxx, toXxx) into line, e.g.
  • We change Match.Pattern.unapply(T) to return R instead of Option<R> by adding PartialFunction to the Pattern type hierarchy. Then we are able to check with isDefinedAt(T) if we are able to unapply an object.
  • It seems odd to me that Pattern0 'extracts itself'. We should consider that Pattern0.unapply() returns Option<Void>. This would be formally correct because Pattern0 extracts zero parts.
  • static fill() methods currently take a Supplier. They should take an object instead of a supplier - will be done in Add static fill(int, T) methods #2182
  • get() should not necessarily a NoSuchElementException in the empty case. For example we want to sneaky throw the underlying exception when calling Try/Future.get() on a failed instance.
  • remove CharSeq.split(...) and rename CharSeq.splitSeq(...) to CharSeq.split(...)
  • replace Future.andThen(Consumer<? super Try<T>> action) with andThen(Function<? super Try<T>, ? extends U> action) (see Scala's Future.andThen(), see also this deprecation discussion) - will be done in Let Future.andThen take a Function instead of a Consumer #2183
  • Lazy.filter() returns an Option, which is basically ok (see Lazy.filter() is not lazy #1861). But we have to ensure that this is compatible with our compositional API, for example For-comprehensions. If we add if-guards to For, we can't yield a Lazy anymore (because filter() returns an Option). However, we could treat Lazy as Iterable in For and don't provide a special For(Lazy) method. - Done in Changed Lazy.filter return type #2179
@danieldietrich danieldietrich added this to the 3.0.0 milestone Aug 15, 2016
@danieldietrich danieldietrich changed the title Remove unnecessary ballast in 3.0.0 Breaking changes in 3.0.0 Aug 26, 2016
@danieldietrich
Copy link
Contributor Author

danieldietrich commented Sep 11, 2016

Consideration removing Kind1/Kind2 from core lib

Javaslang claims to be a functional programming library. The algebraic computation layer is important for some of our users. So I ask the question if we can remove Kind1/Kind2 from the javaslang core module while preserving the algebraic extensions contained in the javaslang-pure module.

Status Quo

Currently Kind1/Kind2 live in the javaslang project. These are recursive type definitions without methods:

public interface Kind1<TYPE extends Kind1<TYPE, ?>, T> {
}

public interface Kind2<TYPE extends Kind2<TYPE, ?, ?>, T1, T2> {
}

These interfaces tag types (e.g. List<T>) that can be decomposed into

  1. The context type (e.g. List)
  2. The value type (e.g. T)

The List interface currently looks like this:

public interface List<T> extends Kind1<List<?>, T>, LinearSeq<T>, Stack<T> {
    ...
}

In javaslang-pure the Monad lifts 'ordinary' functions to functions that operate in a monadic context (read: on Monad):

public interface Monad<M extends Kind1<M, ?>, T> extends Functor<T> {

    <U> Monad<M, U> flatMap(Function<? super T, ? extends Monad<M, U>> mapper);

    @Override
    <U> Monad<M, U> map(Function<? super T, ? extends U> mapper);

    Kind1<M, T> narrow();
}

The narrow function is the important part. It allows us to transform the Monad type back to real object type, which is needed to implement Monad.flatMap().

Example: List

static <T> Monad<List<?>, T> of(List<T> list) {
    return new Monad<List<?>, T>() {
        @Override
        public <U> Monad<List<?>, U> flatMap(Function<? super T, ? extends Monad<List<?>, U>> f) {
            return Monad.of(list.flatMap((T t) -> (List<U>) f.apply(t).narrow()));
        }
        @Override
        public <U> Monad<List<?>, U> map(Function<? super T, ? extends U> f) {
            return Monad.of(list.map(f));
        }
        @Override
        public List<T> narrow() {
            return list;
        }
    };
}

Trying to remove Kind1

Our goal is to remove Kind1 from the javaslang core module, e.g.

public interface List<T> extends LinearSeq<T>, Stack<T> {
    ...
}

The javaslang-pure module is adjusted like that:

public interface Monad<M, T> extends Functor<T> {

    <U> Monad<M, U> flatMap(Function<? super T, ? extends Monad<M, U>> mapper);

    @Override
    <U> Monad<M, U> map(Function<? super T, ? extends U> mapper);

    M narrow();
}

The List Monad implementation is then constructed as follow:

static <T> Monad<List<?>, T> of(List<T> list) {
    return new Monad<List<?>, T>() {
        @Override
        public <U> Monad<List<?>, U> flatMap(Function<? super T, ? extends Monad<List<?>, U>> f) {
            return Monad.of(list.flatMap((T t) -> (List<U>) f.apply(t).narrow()));
        }
        @Override
        public <U> Monad<List<?>, U> map(Function<? super T, ? extends U> f) {
            return Monad.of(list.map(f));
        }
        @Override
        public List<T> narrow() {
            return list;
        }
    };
}

The case (List<U>) f.apply(t).narrow() is unsafe. It is correct but cannot be verified by the compiler because Monad.narrow() return M, which is List<?> instead of List<U>.


Removing Monad.narrow() but keeping Kind1

This leads to:

public interface Monad<M extends Kind1<M, ?>, T> extends Functor<T> {

    <U> Monad<M, U> flatMap(Function<? super T, ? extends Monad<M, U>> mapper);

    @Override
    <U> Monad<M, U> map(Function<? super T, ? extends U> mapper);
}

The List Monad implementation can be made safe by making use of intersection types. We can just add the Kind1 interface to the type List (and name it M) because Kind1 contains no methods.

static <T, M extends List<?> & Kind1<M, ?>> Monad<M, T> of(List<T> list) {
    return new Monad<M, T>() {
        @Override
        public <U> Monad<M, U> flatMap(Function<? super T, ? extends Monad<M, U>> f) {
            return Monad.of(list.flatMap((T t) -> (List<U>) f.apply(t)));
        }
        @Override
        public <U> Monad<M, U> map(Function<? super T, ? extends U> f) {
            return Monad.of(list.map(f));
        }
    };
}

Now narrow() is gone and (List<U>) f.apply(t) is safe.

But we loose a common Monad.narrow() in favor of removing Kind1 (and Kind2) from the javaslang core module. This is not eligible because at some point we may want to return from the abstract algebraic computation layer back to the core library layer.

Having said this, we will stay with Kind1, Kind2 in the core lib and also keep javaslang-pure.


See also

@danieldietrich
Copy link
Contributor Author

CharSeq

  • rename Seq<CharSeq> splitSeq(...) to Seq<CharSeq> split(...) (CharSeq[] split(...) will be removed)

@nfekete
Copy link
Member

nfekete commented Mar 10, 2017

Memoized might be worth keeping public as I actually had a use case, where I defined a new CustomFunctionX type but overridden the FunctionX.memoized() to call Memoized.of with a different map implementation (reference map instead of hashmap).

@danieldietrich
Copy link
Contributor Author

@nfekete We will take it into account. There are some interesting ideas, I opened a separate issue: #1905

@danieldietrich danieldietrich modified the milestones: vavr-0.9.0, vavr-1.0.0 Apr 21, 2017
@danieldietrich danieldietrich modified the milestones: vavr-1.0.0, vavr-0.9.0 May 9, 2017
@valery1707
Copy link
Contributor

I ran into a situation where I need the Lazy#filter to return Lazy and now is not clear whether it will be or not?
#1861 (comment)

@danieldietrich: making filter() lazy looks more consistent to my eyes, so let's do the change in 3.0.0

And in this issue I see this point, but it striked out but not completed.
Is this still on question or Lazy#filter always will return Option?

P.S.
Sorry for terrible english :(

@danieldietrich
Copy link
Contributor Author

@valery1707 Do you have a suggestion what an 'empty' lazily evaluated value could be?
Please also take a look at #1861

I understand declaring a value as 'lazy' means, that there is a value but the evaluation is deferred to the moment the value is needed. Especially we currently can rely on the fact that such a value is always present.

If we allow Lazy to be also empty (read: undefined), it is more like a LazyOption. We could do the same for all other Vavr types, e.g. LazyTry, LazyEither, LazyXyz...

Maybe Lazy should be more like a lazy view or decorator for existing types. E.g. instead of Lazy<T> we might want to have Lazy<Option<T>>.

@nfekete
Copy link
Member

nfekete commented Nov 21, 2017

Is there anything a a LazyOption<T> could solve that a Lazy<Option<T>> cannot? Similarly for LazyTry and LazyEither, etc?

@valery1707
Copy link
Contributor

@danieldietrich I understand your point of view.
I also do not know to represent empty lazy value :)
I solved my problem just through sort of Lazy<Option<T>>.
The question is removed, thanks for the clarification

@danieldietrich
Copy link
Contributor Author

@valery1707 nevertheless, your point is valid. I will think about Lazy (filter) for 1.0.0

@danieldietrich
Copy link
Contributor Author

danieldietrich commented Nov 24, 2017

@nfekete I think Lazy.filter cannot be expressed in the way that the result is lazy

{ // variant 1: still has a sub-optimal filter
    Lazy<Option<T>> lazyOption = Lazy.of(() -> Option.of(...));
    Option<Option<T>> filtered = lazyOption.filter(predicate);
}

{ // variant 2: not lazy anymore, also not what we want to express
    Option<Lazy<T>> optLazy = Option.of(Lazy.of(() -> ...));
    Option<Lazy<T>> filtered = optLazy.filter(predicate);
}

@valery1707
Copy link
Contributor

But we can this:

Lazy<Option<T>> lazyOption = Lazy.of(() -> Option.of(...));
Lazy<Option<T>> filtered = lazyOption.map(o -> o.filter(predicate));

And filtered still be lazy

@danieldietrich
Copy link
Contributor Author

@valery1707 thx for clarification! That's a really nice solution!

We should Lazy.filter accordingly! I created #2178

@danieldietrich
Copy link
Contributor Author

Created separate issues for the remaining tasks. See #2182, #2183
Will close this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants