JDK-8203703 : String::transform
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 12
  • Submitted: 2018-05-23
  • Updated: 2019-01-09
  • Resolved: 2018-11-14
Related Reports
CSR :  
Relates :  
Relates :  
Description
Summary
-------

Provide a String instance method to allow function application of custom
transformations applied to an instance of String.

Problem
-------

Functional programming landed in Java with the introduction of lambdas
in JDK 8. The main advantage of this style of programming is the clarity
it brings to otherwise complex code.  

At the time, the focus was on the internal iteration of collection
object types. Other base objects, such as String, don't have the same
advantage because it's **not possible to extend base objects with new
methods**. The main recourse is to provide static methods that take the
base object as argments. Since static methods can't be chained they
distort readabilty of the resulting code.

```

Example:

    static String naiveDropFirstWord(String string) {
        return List.of(string.split("\\W+"))
                   .stream()
                   .skip(1)
                   .collect(Collectors.joining(" "));
    }

    static String naiveTitleCase(String string) {
        return List.of(string.split("\\W+"))
                   .stream()
                   .transform(s -> s.substring(0, 1).toUpperCase() +
                             s.substring(1).toLowerCase())
                   .collect(Collectors.joining(" "));
    }

    String title = "darn poor title for a book";
    
    String capped = naiveTitleCase(naiveDropFirstWord(title)).concat(".");

Result:

    Poor Title For A Book.
    
```

In this example, the reader is forced to interpret portions of the expression
from the inside out.

Solution
--------

Introduce a new String instance method `transform` that applies a Function
against the string.

```

Example:

    static String naiveDropFirstWord(String string) {
        ...
    }
    
    static String naiveTitleCase(String string) {
        ...
    }
    
    String title = "darn poor title for a book";
    
    String capped = title.transform(Example::naiveDropFirstWord);
                         .transform(Example::naiveTitleCase)
                         .concat(".");

Result:

    Poor Title For A Book.

```

In this example, the steps can be discerned more clearly.

Specification
-------------

```
    /**
     * This method allows the application of a function to {@code this}
     * string. The function should expect a single String argument
     * and produce an {@code R} result.
     *
     * @param f    functional interface to a apply
     *
     * @param <R>  class of the result
     *
     * @return     the result of applying the function to this string
     *
     * @see java.util.function.Function
     *
     * @since 12
     */
    public <R> R transform(Function<? super String, ? extends R> f) {
        return f.apply(this);
    }
```
Comments
Moving to Approved.
14-11-2018

This is missing "Compatibility Kind" selection and "Compatibility risk" description.
13-11-2018

The class java.util.Optional also has public <U> Optional<U> map���(Function<? super T,���? extends U> mapper) Optional as sometimes regarded as a degenerate 0/1 collection, but it is not a java.util.Collection kind of collection.
09-11-2018

I had originally proposed "map", but It had been argued that "map" applies to collections, hence "transform". I have no qualms if the chair strongly recommends "map",
09-11-2018

I didn't see too much discussion on the lists about the name of the method. As noted elsewhere, the type of the "transform" method is the same as the type of the Stream.map method with T bound to String: <R> Stream<R> map���(Function<? super T,���? extends R> mapper)
09-11-2018

Moving to Provisional for the general concept, but I expect any Finalized version to be updated in response to the core-libs (and other) review feedback.
25-09-2018

Noted.
13-09-2018

Hi Jim, It should be ``` public <R> R transform(Function<? super String, ? extends R> f) { ... } ``` and you also need to add primitive variants for int, long and double ``` public int transformToInt(ToIntFunction<? super String> f) { ... } ``` etc for the other variants. We need those variants for the same reasons there is a Stream.mapToInt(), ... Having to add those several variants make this API less appealing IMO.
13-09-2018