Vavr: Provide shorter aliases for static factories

Created on 10 Jul 2016  路  13Comments  路  Source: vavr-io/vavr

From

Tuple.of(1,2,3)
List.of(1,2,3)

To

tuple(1,2,3)
list(1,2,3)

By providing a sort of Aliases like this :

public final class Aliases {

    public static <A, B> Tuple2<A, B> tuple(A a, B b) {
        return Tuple.of(a, b);
    }

    public static <A, B, C> Tuple3<A, B, C> tuple(A a, B b, C c) {
        return Tuple.of(a, b, c);
    }

    public static <T> List<T> list(T element) {
        return List.of(element);
    }

    public static <T> List<T> list(T... elements) {
        return List.of(elements);
    }

   // many more...
}
feature 芦vavr-core禄

Most helpful comment

Yes, breaking the lib is a hard decision.

I think evolution is not possible without changing existing things. Java doesn't do it. Scala did it in the past but got more conservative. Swift 3 will include many breaking changes to make the language better.

We use semantic versioning. Major versions break things by definition. But major versions should not appear often.

Our users need a reliable lib. Having that said, we need to maintain the 2.x streamline. The 3.x streamline will be the next evolution step. I can't foresee all possibilities. Evolution is a feedback-loop considering the outer world.

All 13 comments

I like the idea of static importing, but not sure an Aliases would be the place to do it: it would have a very low cohesion.

@ronanM yes, great idea! We have to take care to return the general types instead of special types, e.g. Option.none() and Option.some() return Option etc.

I would place them in javaslang.API (which is generated) instead of Aliases. Maybe we should also provide f1(Function1), ..., f8(Function8) as shortcut for Function1.of(Function1), ... (this would go adhere with this feature request).

@paplorinc You are right (low cohesion) but on the other hand it would be great to just

import static javaslang.API.*

and have everything at hand :-)

If we don't want to include all variants for all primitives and wrappers and iterables etc, it might work :)

Yes, we should cover:

Functions

  • Function0, ..., Function8 - f0, ..., f8
  • CheckedFunction0, ..., CheckedFunction8 - chk_f0, ..., chk_f8 _<-- better names?_ or just cf0, ...cf8?

Tuples

  • Tuple0, ..., Tuple8 - tuple(), ..., tuple(T1, ..., T8) (but no aliases like pair etc!)

Single-Valued Types

  • Either - left, right
  • Future- future (successful, failed would be too confusing, see Try)
  • Lazy - lazy
  • Option - option, some, none, nothing
  • Try - try_ or _try, success, failure
  • Validation - valid, invalid

Multi-Valued Types

  • And all collections of the last layer of the following overview (e.g. charSeq, array, vector, (not stack), list, stream, queue, hashMap, ...)

collections

:+1: (btw, the tree should be updated with BitSet and PriorityQueue)

Yes, I will create an issue (or I will forget it...)
See javaslang/javaslang-docs#25

The scope changed:

@paploric stated:

public static <T> $OptionType<T> some(T value) {
do we need these?
What's the difference between importing statically Option or API?
I mean these are already available to us by importing Option statically.

This is a valid point. Several types already have the static factory methods with similar names:

  • [x] controls (Either, Option, Try, Validation)
  • [x] Future has successful() and failed() (Promise has the same method names...)
  • [ ] Collections
  • [ ] Tuples
  • [ ] Functions

We still have additional problems here - some API is ambiguous, e.g.

// returns a success
public static <T> $FutureType<T> future(T result) { ... }

// returns a failure
public static <T> $FutureType<T> future(Throwable exception) { ... }

It was a design decision to use consequently use of throughout our whole codebase, e.g.

  • Tuple.of(1, true, "nice")
  • List.of(1, 2, 3)
  • Try.of(() -> computation())

Main reasons:

  • Java also uses the of API. It looks familiar to the user.
  • Lower case 'constructors' look alien to Java developers.
  • Scala has companion objects which give use upper case constructors without the use of new - that is what we really want. But the upper-case names are already taken by the Pattern Match API in order to deconstruct objects.
  • Minor: Static imports are always a few keystrokes more for the developer, even with an IDE.

But: The of notation is not concise enough.

Like @paplorinc said, we do not need to duplicate the existing API. What we really want are shortcuts for the of factory methods only.

I would like to have first char upper-case static factory method names within javaslang.API, e.g.

  • Tuple(1, 2), Tuple(1, 2, 3, 4)
  • Function1(obj::methodRef), CheckedFunction1(obj::methodRef)
  • Try(() -> computation()), Option(value), Future(() -> computation())
  • List(1, 2, 3)
  • HashMap(1, "a", 2, "b", 3, "c")

This would go adhere with API.Match() and API.Case() etc.

To this day static factory methods for (most) collection _interfaces_ were out of scope, e.g.

  • Seq(1, 2, 3)
  • Set(1, 2, 3)
  • Map(1, "a", 2, "b", 3, "c")

But maybe it would be nice to have these too.


Q: So, what to do with the names of generated patterns? They will clash with the new API.

A: We will break backward compatibility and change the annotation processor of javaslang-match. We will generate a preceding $ for patterns. This goes adhere with the use of $ for standard patterns like

  • $() - any
  • $(value) - equality
  • $(predicate) - condition

Example:

T result = Match(value).of(
    Case($Tuple($(1), $(true), $("nice")), (v1, v2, v3) -> ...),
    Case($List($(1), $()), (head, tail) -> ...)
);

We determine these _specialities_:

  • $Tuple($(Tuple(1, 2, 3)) will contain a $Tuple pattern and a Tuple constructor. Hard to read?
  • $List($(), $()) deconstructs _head_ element and _tail_ list
  • List(v1, v2) constructs a List containing two values v1, v2

The bottom line is that this will be a breaking change that will be targeted for 3.0.0

However, the first char upper case constructors can be already introduced but they will raise problems when used in conjunction with the Match API (, e.g. import static javaslang.API.*;).

oh boy, I really love breaking changes :D

Yes, breaking the lib is a hard decision.

I think evolution is not possible without changing existing things. Java doesn't do it. Scala did it in the past but got more conservative. Swift 3 will include many breaking changes to make the language better.

We use semantic versioning. Major versions break things by definition. But major versions should not appear often.

Our users need a reliable lib. Having that said, we need to maintain the 2.x streamline. The 3.x streamline will be the next evolution step. I can't foresee all possibilities. Evolution is a feedback-loop considering the outer world.

There is one thing that still bothers me - we will have mixed upper case and lower case factory methods, e.g.

  • upper case API.Option() but lower case Option.some(), Option.none()

This idea is still unfinished... We need to solve this until 3.0.0

@paplorinc asked:

There is one thing that still bothers me - we will have mixed upper case and lower case factory methods, e.g.

Could you please explain the need for upper-camel? If it's simply to model Scala's companion constructor, we could simply translate that to statically-importable lowercase static factories, e.g. option, some, checkedFunction.
About API, well, it could have a prefix of e.g. $ or _, but still lowercase, e.g. $option.

These are my thoughts:

First view of things: Have consistent/equal naming throughout all Javaslang classes and interfaces

Deconstructors (read: unapply patterns) and constructors are _dual_. This should be reflected by the name. I think we have two options: both upper- or both lower case. Distinguishing them is mandatory. A prefix should work fine ('$' would integrate well for the patterns).

These are our options:

  • All methods upper or all lower case, e.g. Some()/$Some() vs some()/$some()
  • Define all methods within javaslang.API or all within related types or a hybrid solution

Each type has several of and ofAll methods in general. When adding new factory methods we concentrate on of(T) and of(T...).

Looking at Option we will take one of these approaches:

  • API.Option(T), API.Option(T...) and Patterns.$Option()
  • API.option(T), API.option(T...) and Patterns.$option()
  • Option.Option(T), Option.Option(T...) and Patterns.$Option()
  • Option.option(T), Option.option(T...) and Patterns.$option()

Second view of things: We could _define_ the following in _idiomatic Javaslang_

I like to just import static javaslang.API.* and then start to write Javaslang using Match, For and almost all of the Values, Tuples and (Checked)Functions.

  • Static factory methods related to types are lower case and not intended to be imported statically, e.g. we always use Option.some(...) instead of some(...). This would go adhere with of and ofAll.
  • The javaslang.API is intended to be used as static import _only_. E.g. we write For and Match instead of API.For and API.Match.
  • API contains the keywords of the Java _slang_. These keywords are all upper-case in order to circumvent name-clashes with default Java. E.g. we have For and Try(() -> computation) instead of for and try(() -> computation()).
  • We would then add also API.Some() and API.None() in addition to Option.some() and Option.none().

In other words Some(...) and Option.some() are different in the way they are used, the former as static import, the latter full qualified. The case indicates the usage.

Patterns are prefixed upper-case, e.g. $Some(...).

May be this issue must be in milestone 2.1.0 as PR?

Right!

Was this page helpful?
0 / 5 - 0 ratings