Wednesday, December 26, 2018

Compact Number Formatting Comes to JDK 12

JDK 12 Early Access Build 24 introduces support for Compact Number Formatting. The JDK-8188147 (Compact Number Formatting support) CSR's "Summary" is the simple sentence, "Adding support for the compact/short number formatting in JDK." That same CSR also provides a detailed "Solution" section that provides background on providing numbers in multiple compact forms for each Locale and the constructs/APIs added to the JDK to support this functionality (new class, new enum, new methods, etc.)

The representations of compact and short formats of numbers in each locale are based on the Unicode Common Locale Data Repository (CLDR). A newly added class, java.text.CompactNumberFormat, has class-level Javadoc comments that provide quite a bit of detail regarding how numbers are expressed in "short" and "long" compact number formats. That class's Javadoc comments also specify compact number patterns, formatting, parsing, and rounding (RoundingMode.HALF_EVEN by default) related to custom number formats.

In the request for review of the addition of compact number formatting to JDK 12, Nishit Jain writes:

The existing NumberFormat API provides locale based support for formatting and parsing numbers which includes formatting decimal, percent, currency etc, but the support for formatting a number into a human readable or compact form is missing. This RFE adds that feature to format a decimal number in a compact format (e.g. 1000 -> 1K, 1000000 -> 1M in en_US locale), which is useful for the environment where display space is limited, so that the formatted string can be displayed in that limited space. It is defined by LDML's specification for Compact Number Formats.

http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats

It is probably easiest to understand compact number formatting via code example. The following class (CompactNumberFormatDemo) was compiled and executed against JDK 12 Early Access Build 24 and is also available on GitHub.

package dustin.examples.jdk12.format;

import static java.lang.System.out;

import java.text.NumberFormat;
import java.util.Locale;

/**
 * Demonstrate Compact Number Format support added to
 * JDK 12 as of Early Access Build 24 (see also
 * JDK-8177552: Compact Number Formatting support).
 */
public class CompactNumberFormatDemo
{
   private static void demonstrateCompactNumberFormatting(final long numberToFormat)
   {
      final NumberFormat numberFormatDefault
         = NumberFormat.getCompactNumberInstance();
      final NumberFormat numberFormatUsLong
         = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
      final NumberFormat numberFormatUkShort
         = NumberFormat.getCompactNumberInstance(Locale.UK, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatUkLong
         = NumberFormat.getCompactNumberInstance(Locale.UK, NumberFormat.Style.LONG);
      final NumberFormat numberFormatFrShort
         = NumberFormat.getCompactNumberInstance(Locale.FRANCE, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatFrLong
         = NumberFormat.getCompactNumberInstance(Locale.FRANCE, NumberFormat.Style.LONG);
      final NumberFormat numberFormatGrShort
         = NumberFormat.getCompactNumberInstance(Locale.GERMANY, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatGrLong
         = NumberFormat.getCompactNumberInstance(Locale.GERMANY, NumberFormat.Style.LONG);
      final NumberFormat numberFormatItShort
         = NumberFormat.getCompactNumberInstance(Locale.ITALY, NumberFormat.Style.SHORT);
      final NumberFormat numberFormatItLong
         = NumberFormat.getCompactNumberInstance(Locale.ITALY, NumberFormat.Style.LONG);

      out.println("Demonstrating Compact Number Formatting on '" + numberToFormat + "':");
      out.println("\tDefault:  " + numberFormatDefault.format(numberToFormat));
      out.println("\tUS/Long:  " + numberFormatUsLong.format(numberToFormat));
      out.println("\tUK/Short: " + numberFormatUkShort.format(numberToFormat));
      out.println("\tUK/Long:  " + numberFormatUkLong.format(numberToFormat));
      out.println("\tFR/Short: " + numberFormatFrShort.format(numberToFormat));
      out.println("\tFR/Long:  " + numberFormatFrLong.format(numberToFormat));
      out.println("\tDE/Short: " + numberFormatGrShort.format(numberToFormat));
      out.println("\tDE/Long:  " + numberFormatGrLong.format(numberToFormat));
      out.println("\tIT/Short: " + numberFormatItShort.format(numberToFormat));
      out.println("\tIT/Long:  " + numberFormatItLong.format(numberToFormat));
   }

   /**
    * Main demonstration executable.
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      demonstrateCompactNumberFormatting(15);
      demonstrateCompactNumberFormatting(150);
      demonstrateCompactNumberFormatting(1500);
      demonstrateCompactNumberFormatting(15000);
      demonstrateCompactNumberFormatting(150000);
      demonstrateCompactNumberFormatting(1500000);
      demonstrateCompactNumberFormatting(15000000);
   }
}

When executed, the above code writes the following to standard output:

Demonstrating Compact Number Formatting on '15':
 Default:  15
 US/Long:  15
 UK/Short: 15
 UK/Long:  15
 FR/Short: 15
 FR/Long:  15
 DE/Short: 15
 DE/Long:  15
 IT/Short: 15
 IT/Long:  15
Demonstrating Compact Number Formatting on '150':
 Default:  150
 US/Long:  150
 UK/Short: 150
 UK/Long:  150
 FR/Short: 150
 FR/Long:  150
 DE/Short: 150
 DE/Long:  150
 IT/Short: 150
 IT/Long:  150
Demonstrating Compact Number Formatting on '1500':
 Default:  2K
 US/Long:  2 thousand
 UK/Short: 2K
 UK/Long:  2 thousand
 FR/Short: 2 k
 FR/Long:  2 millier
 DE/Short: 1.500
 DE/Long:  2 Tausend
 IT/Short: 1.500
 IT/Long:  2 mille
Demonstrating Compact Number Formatting on '15000':
 Default:  15K
 US/Long:  15 thousand
 UK/Short: 15K
 UK/Long:  15 thousand
 FR/Short: 15 k
 FR/Long:  15 mille
 DE/Short: 15.000
 DE/Long:  15 Tausend
 IT/Short: 15.000
 IT/Long:  15 mila
Demonstrating Compact Number Formatting on '150000':
 Default:  150K
 US/Long:  150 thousand
 UK/Short: 150K
 UK/Long:  150 thousand
 FR/Short: 150 k
 FR/Long:  150 mille
 DE/Short: 150.000
 DE/Long:  150 Tausend
 IT/Short: 150.000
 IT/Long:  150 mila
Demonstrating Compact Number Formatting on '1500000':
 Default:  2M
 US/Long:  2 million
 UK/Short: 2M
 UK/Long:  2 million
 FR/Short: 2 M
 FR/Long:  2 million
 DE/Short: 2 Mio.
 DE/Long:  2 Million
 IT/Short: 2 Mln
 IT/Long:  2 milione
Demonstrating Compact Number Formatting on '15000000':
 Default:  15M
 US/Long:  15 million
 UK/Short: 15M
 UK/Long:  15 million
 FR/Short: 15 M
 FR/Long:  15 million
 DE/Short: 15 Mio.
 DE/Long:  15 Millionen
 IT/Short: 15 Mln
 IT/Long:  15 milioni

The compact number format support that has been added to JDK 12 via Early Access Build 24 allows for formatting and parsing numeric representations in a locale-specific "long" or "short" compact forms.

Saturday, December 22, 2018

The Brief but Complicated History of JDK 12's String::transform Method

It was recently proposed that the Java preview feature Raw String Literals (JEP 326) be removed from JDK 12 and it is now official that the preview feature will be removed (version 25 of Java SE 12 [JSR 386] removes it). Several methods had been added to the JDK String class to support this feature. Those methods that were added to versions of the JDK prior to JDK 12 [such as String::lines] are likely to remain available even after the raw string literals preview feature has been removed. However, it has already been decided that one method added to String in JDK 12 (String::align) should be removed from JDK 12 as part of removing raw string literals. The method String::transform was added to JDK 12 and the remainder of this post looks at String::transform, as currently implemented in JDK 12, in more detail and discusses why its already controversial short history implies that it could be a potential candidate for removal along with raw string literals.

The current String::transform implementation has been available in JDK 12 Early Access Builds since Build 22 (Build 24 [15 December 2018] is latest available build as of this writing) and was introduced via JDK-8203442 ("String::transform").

There was quite a bit of discussion related to this method being added to the JDK. The following bullets outline key discussion points.

  • Jim Laskey wrote that the "originating goal" of String::transform was to "allow custom alignment methods for those developers not satisfied with String::align()"
  • Other messages further describe the motivation for, intent of, and benefits of String::transform:
    • Rémi Forax wrote, "...it's nice to be able to be able to write code fluently from left to right..."
    • Jim Laskey wrote, "String::transform was intended to facilitate custom manipulation (alignment) of raw string literals, in the most string generalized way."
    • The "Description" of JDK-8203442 states, "The String::transform instance method allows the application of a lambda function to a string."
    • JDK-8203703 supplies examples to illustrate that "...steps can be discerned more clearly" with String::transform than with static methods in which the "reader is forced to interpret portions of the expression from the inside out."
  • String::transform originally returned String, but then was changed to return Object and Jim Laskey wrote about that change, "'transform' became generic when the case was made that other types might also be relevant." He concluded, "I might be led back to just supporting String."
  • The naming of String::transform has been challenging with some of the following names proposed (listed in alphabetic order):
  • Rémi Forax has written that "more variants (transformToInt, transformToLong, transformToDouble) [are needed] to be useful."
  • Brian Goetz has described why the current plan is to implement this functionality via the method String::transform rather than an operator such as |>.
  • Stuart Marks has written that "this particular decision [String::transform] sets a precedent for the use of the name 'transform' for methods that do similar things on other classes" and references JDK-8140283 and JDK-8214753:
    • JDK-8140283 proposes the addition of the "chain" method for Stream and Optional to "mitigate" the "disrupt[ion of] the linear flow of the pipeline stages" when using methods that acts upon a Stream or Optional and return something that is itself "chainable").
    • JDK-8214753 proposes the addition of "Optional::transform" that would allow for "an arbitrary operation on an Optional."
  • There was some confusion and consternation related to how String::transform was added to OpenJDK 12, but Stuart Marks's message summarizes the events leading to the addition of this method.
    • A particularly interesting sentence in Marks's message states (I have added the emphasis): "While this API point stands on its own, this is really part of Jim's RSL work which includes several API additions to String, and which will likely have a significant effect on how String literals are used in Java code."
  • Tomasz Linkowski has pointed out that it's likely that String::transform (and any similar method added to Stream) will get used in select cases where there are easier ways to do the same thing already without the new method. The examples he provides of potential misuse of String::transform are "string.transform(String::toLowerCase)" and "stream.chain(s->s.map(mapper))".

Two online examples demonstrate how String::transform might be used in its most common use cases:

  • JDK-8203703 ("String::transform") provides a "Solution" example that demonstrates how String::transform can improve code readability by allowing for operations acting on Strings to be read in order from left-to-right rather than being read "from the inside out."
  • A message on the core-libs-dev mailing list provides an example of using String::transform to convert a String into an instance of a class other than String.

Stephen Colebourne asked the same question I was wondering when I read that raw string literals were to be removed from JDK 12: "Is String::transform going to be removed as well given the removal of raw strings and its controversial nature?" Although I have not seen anything authoritative and definitive regarding whether String::transform will remain in JDK 12, there are three pieces of evidence that lead me to think it will be staying.

  1. I have not seen anything saying that String::transform, which is already in JDK 12 as of Early Access Build 22, is to be removed. There are issues written to remove compiler support associated with raw string literals and even to remove another String method (String::align), but I'm not aware of a similar issue written for String::transform.
  2. It has been stated that while String::transform was added as part of the raw string literal work, it was also stated that String::transform "stands on its own."
  3. The two examples I cited earlier on how to use this method do not rely on or require raw string literals. In other words, the method can be used regardless of the presence or absence of raw string literals.

String::transform has not been around for a long time (less than one year), but it already has significant history. The method is available currently in JDK 12 (since Early Access Build 22) and I suspect it will remain part of String's API despite the removal of raw string literals from JDK 12.

Tuesday, December 11, 2018

Dropping Raw String Literals from JDK 12

It has been proposed that raw string literals (preview) be dropped from JDK 12 (which enters Rampdown Phase One on December 13). Brian Goetz has written a detailed description of the motivations for dropping this preview feature (JEP 326). There is also discussion on this on the Java subreddit. In the post "JSR 386 (Java SE 12) JEP Propose to Drop: 326: Raw String Literals (Preview)," Iris Clark writes that JEP 326 "of scope 'SE' has been Proposed to Drop for Java SE 12."

In Goetz's explanation for the proposal to remove raw string literals preview functionality from JDK 12, he writes, "The Preview Feature mechanism is intended for features for which there is a high confidence that the feature is 'done', and the likelihood that significant changes would be made before making the feature permanent is low." Goetz adds, "I am no longer convinced that we've yet got to the right set of tradeoffs between complexity and expressiveness, or that we've explored enough of the design space to be confident that the current design is the best we can do. By withdrawing, we can continue to refine the design, explore more options, and aim for a preview that actually meets the requirements of the Preview Feature process (JEP 12)."

Goetz also provides a sample of the feedback items they've received regarding raw string literals preview design and implementation. He concludes the message with the statement, "Discussion on the technical details of this feature can continue to take place on the amber-* lists" (amber-dev, amber-spec-comments, amber-spec-experts, and amber-spec-observers).

It sounds like there are still plans for raw string literals to come to Java, but they'll be implemented differently than they are currently in JDK 12 Early Access Builds.