SimpleIO in JEP draft 8323335

Brian Goetz brian.goetz at oracle.com
Tue Feb 20 15:12:42 UTC 2024


> I guess, this is not about "give me more" but about "design a thing 
> that will be really useful". If SimpleIO will not be satisfactory for 
> solving 90% of student problems, then probably there's no point in 
> introducing it at all?

I'm willing to have that discussion!

We could draw the line at "nothing", which is the status quo. That's 
always an option.

We could draw the line at "println only".  That is surely better than 
nothing, succeeds 100% at hitting a very very limited goal, but is very 
.. limited.  But it is a perfectly viable candidate.

This proposal goes one step farther, giving the dual of println.  It 
also seems a viable candidate, and also seems better than nothing. None 
of the proposals involving parsing numbers seem remotely good enough.  
So they are all, currently, worse than nothing.

Now, better-than-nothing is surely not the bar, though.  One of the 
other considerations is "if we do more later, will progress be colinear 
with this."  Given the degree of restraint shown here (line-based 
console IO <--> String), it is hard to imagine concluding later "wow, we 
goofed there."  Could happen, but seems unlikely, because we are 
sticking to sensible primitives.

> Teaching Integer::parseInt or new Scanner() sounds contradicting to 
> the idea of hiding irrelevant details like classes and static methods 
> from the students until they need them.

But, hiding all irrelevant detail is not the goal!  Otherwise we'd be 
saying "Ask ChatGPT to write your program for you!"  What we're looking 
to hide (temporarily) is *linguistic* mechanisms whose value is in 
*structuring larger programs*, until such mechanisms *provide value to 
the program*.  Packages, access control, static, etc, are 
program-organization concepts, which benefit large programs but are a 
tax on small ones.  When your program is six lines long, it doesn't need 
help organizing itself, and even if it did, these things don't help yet, 
they just feel like a tax.

That's not to say we can't also try to simplify more things; this is the 
*first* JEP in this area, not the last.  But mission-creep is a real 
risk here, and whenever the goal is as diffuse as "simplicity", which 
everyone interprets their own way, mission-creep is almost inevitable.  
So I don't object to "I want to solve this problem too" (we can keep 
talking, the JDK is a living entity), but I prefer to avoid "If you 
don't solve this problem, it's not worth doing anything."  Because 
there's always more we could do.

Here's a thought experiment: what concept would we want to guide users 
towards for going from string to number?  My favorite is exposing the 
pattern dual of Integer::toString.   The existing Integer::toString is a 
function int -> String; like most total functions, it has a pattern dual:

     if (s instanceof Integer.toString(int i)) {
        // use i
     }

This is already much better than `Integer::parseInt`, because (a) it 
captures the inherent partiality of the operation, (b) you can't sweep 
the failure under the rug by ignoring NumberFormatException, and (c) the 
duality with the function `Integer::toString` is obvious; they are two 
sides of the same coin.

But there are many other possibilities, nearly all better than Scanner.  
Its a totally valid discussion, but I'm not sure we should gate this 
installment on it?



> Because you'll need to explain what is Integer and what is parseInt. 
> Or probably you can say "don't care now, just write it this way, we'll 
> cover this later". But is it much different from public static void 
> main? Why did we start all of this in the first place?
>
> When I was a child, I learned BASIC, and I remember that one could ask 
> a number from a user typing simply INPUT X. The concept of string 
> variables like X$ was introduced much later, so in my mind it was 
> completely ok that we can ask for a number, without an intermediate 
> step like ask for a string, then convert it to the number. Not sure if 
> it was pedagogically correct but it certainly simplified my first 
> steps in computer programming (well, paper programming in fact, as I 
> had no computer).
>
> With best regards,
> Tagir Valeev.
>
> On Tue, Feb 20, 2024 at 3:34 PM Brian Goetz <brian.goetz at oracle.com> 
> wrote:
>
>     What you're really saying is "I want more than you're giving me." 
>     And there's nothing wrong with wanting more.  You seem to have
>     mostly accepted that the primitive is "read a line of input as a
>     string", but it leaves you with a problem: you don't want to read
>     strings, you want to read numbers.  That's all fine.
>
>     But the flip side of "this isn't good enough, give us more" is
>     that if there isn't a "more" that is good enough to meet the the
>     bar, you're going to get ... nothing.  It's a foundational design
>     principle for Java (thanks James!) that if you don't know the
>     right thing to do, then don't do anything (yet).  The alternatives
>     that have been proposed (all of which we already went through
>     before they came around again here) did not meet the bar.  This is
>     what meets the bar.  It may not be as much as you want, but it is
>     something, and it combines with all the other possible next steps.
>
>     As a teacher, you have many choices.  You can keep doing what
>     you've been doing, teaching Scanner; many teachers will.  Or you
>     could distribute your own library of convenience methods -- many
>     teachers do.  Or you could teach Integer::parseInt, which is
>     messy, but has the benefit of being exactly as messy as the
>     problem is -- which is also a useful lesson.  Or, or, or, or.
>
>     And if you don't like the magic static import, don't use it!  Tell
>     your students to use `SimpleIO::readAStringPlease`.  We are not
>     trying to create a beginners dialect here.
>
>     All of this is to say: we are not trying to put out a One True
>     Only Way To Teach Java.  We're smoothing out the path in a way
>     that admits many teaching paths, including ignoring all this
>     stuff.  Is there more that could be done?  Of course.  And when we
>     have a *good* candidate for what the next hundred feet of onramp
>     looks like, we will proceed.  And I am confident that it will not
>     conflict with these first hundred feet, because -- how could it?
>
>
>
>
>
>     On 2/20/2024 6:44 AM, Cay Horstmann wrote:
>>     I am one of the people who writes books for beginners. I have a
>>     whole bunch of example programs that involve reading numbers.
>>     Professors adopting my books have a ton of exercises that involve
>>     reading numbers. I can't ignore reading numbers.
>>
>>     I agree that input and println are reasonable primitives for
>>     beginners, and that number parsing can be done in a separate
>>     step. But if that parsing step is not simple for beginners, I
>>     don't think input will find much use for beginners either.
>>
>>     For my books, I need to decide what to do in the (n + 1)st
>>     edition. Should I stick with
>>
>>     Scanner in = new Scanner(System.in);
>>     ...
>>     System.out.print("How old are you? ");
>>     int age = in.nextInt();
>>
>>     or switch to
>>
>>     println("How old are you?");
>>     int age = in.nextInt();
>>
>>     or go all the way to
>>
>>     int age = Integer.parseInt(input("How old are you"));
>>
>>     I have no conceptual problem with in.nextInt(). I need to explain
>>     method calls early on, so that students can work with strings.
>>
>>     With the new way, I have a different problem. Now I need to
>>     explain to students that they can call an unqualified input, but
>>     parseInt needs to be qualified. And I have to accelerate the
>>     coverage of static methods.
>>
>>     As Brian says, there are too many conflicting goals.
>>
>>     If the goal is simplicity and consistency, it would be more
>>     useful not to use a magic static import. If SimpleIO.input is too
>>     long, it could be IO.in, with IO in java.lang.
>>
>>     If the goal is convenience, it would be better to have more
>>     magically statically imported methods, in particular parseInt,
>>     parseDouble. Or readAnInt, readADouble...
>>
>>     Cheers,
>>
>>     Cay
>>
>>
>>     On 19/02/2024 18.06, Brian Goetz wrote:
>>>     There's a reason there are so many opinions here: because the
>>>     goals are in conflict.  Everyone wants simplicity, but people
>>>     don't agree on what "simple" means.  (Cue the jokes about "I
>>>     would simply not write programs with bugs.")
>>>
>>>     Yes, getting numbers from the user is a basic task. But it is
>>>     not, in any way, simple!  Because reading numbers from the input
>>>     is invariably complected with discarding things that are
>>>     "acceptably non-numbery" (e.g., whitespace), which is neither
>>>     simple nor usually terribly well documented.  We've all
>>>     encountered the problem in many language runtimes where reading
>>>     a number using the "friendly way" leaves the input in a state
>>>     that requires fixing or yields surprises for the next operation.
>>>
>>>     This is because reading a number from an input stream is not any
>>>     sort of primitive; it is the composite of reading from the
>>>     input, deciding what to skip, deciding when to stop reading,
>>>     converting to another type, deciding what state to leave the
>>>     input stream in, and deciding what to do if no number could be
>>>     found (or if the number was too big to fit into an int, etc.) 
>>>     This is not^3 simple!
>>>
>>>     C starts with a simple and principled answer, which is that the
>>>     IO primitive is getchar() and putchar(). Reading or writing one
>>>     character is unquestionably a primitive.  (But also, unless you
>>>     are writing `cat`, no one wants to program with getchar and
>>>     putchar, because it's too primitive.)
>>>
>>>     One can make a reasonable case for "write a line / read a line"
>>>     being sensible primitives.  They are simple enough: no parsing,
>>>     no deciding what to throw away, no possible errors other than
>>>     EOF, it is clear what state you leave the stream in.  These may
>>>     not be what the student wants, but they are primitives a student
>>>     can deal with without having to understand parsing and error
>>>     handling and statefulness yet.
>>>
>>>          String s = getALine();
>>>          printALine(s);
>>>
>>>     is a program every student can reason about.
>>>
>>>     But, it is true that dealing in strings, while honest and
>>>     simple, is not always what the student wants.  But herein lies
>>>     the strongest argument for not trying to reinvent Scanner here:
>>>     the ability to read numbers makes the complexity of the problem,
>>>     and hence of the API, much much bigger.  (Scanner was very well
>>>     intentioned, and was not written by children, and yet none of us
>>>     want to use it.  That's a sign that a one-size-fits-all magic
>>>     input processing system is harder than it looks, and for
>>>     something that is explicitly aimed at beginners, is a double
>>>     warning sign.)
>>>
>>>     I could imagine someone suggesting "why don't you just add
>>>     `readLineAsInt`".  But what would happen next? Well, there would
>>>     be a million requests (including from folks like Cay) of "you
>>>     should add X", and then the result is a mishmash jumble of an
>>>     API (that's already terrible), but worse, it's an onramp that
>>>     leads to nowhere.  Once the user's needs are slightly more
>>>     complicated, they are nowhere.
>>>
>>>     Remi has it absolutely right (yes, I really said that) with
>>>
>>>>     The classical program is:
>>>>        input -> strings -> objects -> strings -> output
>>>
>>>     We do not do users a favor by blurring the distinction between
>>>     "input -> string" and "string -> object", and because the latter
>>>     is so much more open-ended than the former, the latter infects
>>>     the former with its complexity if we try.
>>>
>>>     Is this simple API the most wonderful, be-all of APIs?  Of
>>>     course not.  But it is a sensible set of primitives that users
>>>     can understand and *build on* in a transparent way.
>>>
>>>     Some teachers may immediately reach for teaching
>>>     Integer::parseInt; that's a reasonable strategy, it exposes
>>>     students to the questions of "what happens when preconditions
>>>     fail", and the two compose just fine.  But maybe you don't like
>>>     Integer::parseInt for some reason.  Another way to teach this is
>>>     to have them write it themselves.  This will expose them to all
>>>     sorts of interesting questions (what about whitespace? what
>>>     about double negatives?), but of course is also throwing in the
>>>     deep end of the pool. But SimpleIO::readMeALinePlease is
>>>     agnostic; it works with both approaches.
>>>
>>>     Could the JDK use some better tools for parsing? Sure; pattern
>>>     matching has a role to play here, a `String::unformat` would be
>>>     really cool, and I love parser combinators.  All of this can
>>>     happen in the future, and none have the effect of making this
>>>     API look like yet another white elephant like Scanner. Because
>>>     it focused purely on the basics.
>>>
>>>
>>>     On 2/19/2024 7:25 AM, Remi Forax wrote:
>>>>     I agree with Brian here,
>>>>     as a teacher, you have to talk about parsing and formatting,
>>>>     those should not be hidden.
>>>>
>>>>     The classical program is:
>>>>        input -> strings -> objects -> strings -> output
>>>>
>>>>     Rémi
>>>>
>>>>     ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>>>>
>>>>
>>>>         *From: *"Tagir Valeev" <amaembo at gmail.com>
>>>>     <mailto:amaembo at gmail.com>
>>>>         *To: *"Cay Horstmann" <cay at horstmann.com>
>>>>     <mailto:cay at horstmann.com>
>>>>         *Cc: *"Brian Goetz" <brian.goetz at oracle.com>
>>>>     <mailto:brian.goetz at oracle.com>, "amber-dev"
>>>>     <amber-dev at openjdk.org> <mailto:amber-dev at openjdk.org>
>>>>         *Sent: *Monday, February 19, 2024 10:09:35 AM
>>>>         *Subject: *Re: SimpleIO in JEP draft 8323335
>>>>
>>>>         I agree that simple methods to get numeric input are
>>>>     essential for beginners. They should not be distracted with a
>>>>     complex ceremony. Instead, they should be able to learn control
>>>>     flow statements and simple algorithms as soon as possible,
>>>>     having a simple way to get numbers from the user.
>>>>         With best regards,
>>>>         Tagir Valeev.
>>>>
>>>>         On Mon, Feb 19, 2024 at 9:10 AM Cay Horstmann
>>>>     <cay at horstmann.com> <mailto:cay at horstmann.com> wrote:
>>>>
>>>>             Yes, that's what I am saying. If scanners live in vain,
>>>>     stick with a subset of the Console methods. Use its readLine.
>>>>     Make it so that SimpleIO uses System.console(). And add print
>>>>     and println to Console.
>>>>
>>>>             The JEP talks about being able to start programming
>>>>     without having to know about static methods. How does a
>>>>     beginner read a number? With Integer.parseInt(readLine(prompt))?
>>>>
>>>>             What about locales? Is print/println localized?
>>>>     Console.printf is. If so, how are beginners from around the
>>>>     world supposed to read localized numbers? With
>>>>     NumberFormat.getInstance().parse(readLine(prompt))?
>>>>
>>>>             Adding localized readInt/readDouble to SimpleIO might
>>>>     do the trick. Do they consume the trailing newline? (The
>>>>     equivalent Scanner methods don't, which is definitely a sharp
>>>>     edge for beginners.)
>>>>
>>>>             On 18/02/2024 23.08, Brian Goetz wrote:
>>>>             > OK, so is this really just that that you are
>>>>     bikeshedding the name?  Renaming `input` to `readLine`?
>>>>             >
>>>>             > This is a perfectly reasonable naming choice, of
>>>>     course, but also, not what you suggested the first time around:
>>>>             >
>>>>             >  > ... "a third API" ...
>>>>             >
>>>>             >  > ... "there are two feasible directions" ...
>>>>             >
>>>>             > So what exactly are you suggesting?
>>>>             >
>>>>             >
>>>>             >
>>>>             > On 2/18/2024 5:03 PM, Cay Horstmann wrote:
>>>>             >> Like I said, either the scanner methods or the
>>>>     console methods are fine.
>>>>             >>
>>>>             >> I am of course aware of the utility/complexity of
>>>>     Scanner, and can understand the motivation to have a
>>>>     simpler/feebler behavior in SimpleIO. Like the one in Console.
>>>>             >>
>>>>             >> You don't have to "get a console". A
>>>>     SimpleIO.readLine method can just invoke readLine on the system
>>>>     console.
>>>>             >>
>>>>             >> My objection is to add yet another "input" method
>>>>     into the mix. "input" is weak. Does it read a token or the
>>>>     entire line? Does it consume the newline? And if it does just
>>>>     what readLine does, why another method name? Because "input" is
>>>>     three characters fewer? Let's not count characters.
>>>>             >>
>>>>             >> On 18/02/2024 22.43, Brian Goetz wrote:
>>>>             >>> I think you are counting characters and not
>>>>     counting concepts.
>>>>             >>>
>>>>             >>> Scanner has a ton of complexity in it that can
>>>>     easily trip up beginners.  The main sin (though there are
>>>>     others) is that input and parsing are complected (e.g.,
>>>>     nextInt), which only causes more problems (e.g., end of line
>>>>     issues.) Reading from the console is clearly a () -> String
>>>>     operation.  The input() method does one thing, which is get a
>>>>     line of text.  That's simple.
>>>>             >>>
>>>>             >>> Integer.parseInt (or, soon, patterns that match
>>>>     against string and bind an int) also does one thing: convert a
>>>>     string from int.  It may seem verbose to have to do both
>>>>     explicitly, but it allows each of these operations to be
>>>>     simple, and it is perfectly obvious what is going on. On the
>>>>     other hand, Scanner is a world of complexity on its own.
>>>>             >>>
>>>>             >>> Console::readLine is nice, but first you have to
>>>>     get a Console. ("Why can I print something without having to
>>>>     get some magic helper object, but I can't do the same for
>>>>     reading?")  What we're optimizing for here is conceptual
>>>>     simplicity; the simplest possible input method is the inverse
>>>>     of println.  The fact that input has to be validated is a fact
>>>>     of life; we can treat validation separately from IO (and we
>>>>     should), and it gets simpler when you do.
>>>>             >>>
>>>>             >>> On 2/18/2024 4:12 PM, Cay Horstmann wrote:
>>>>             >>>> I would like to comment on the simplicity of
>>>>     https://openjdk.org/jeps/8323335 for beginning students.
>>>>             >>>>
>>>>             >>>> I am the author of college texts for introductory
>>>>     programming. Like other authors, I introduce the Scanner class
>>>>     (and not Console) for reading user input. Given that students
>>>>     already know about System.out, it is simpler to call
>>>>             >>>>
>>>>             >>>> System.out.print("How old are you? ");
>>>>             >>>> int x = in.nextInt(); // in is a Scanner
>>>>             >>>>
>>>>             >>>> than
>>>>             >>>>
>>>>             >>>> int x = Integer.parseInt(console.readLine("How old
>>>>     are you? "));
>>>>             >>>>
>>>>             >>>> or with the JEP draft:
>>>>             >>>>
>>>>             >>>> int x = Integer.parseInt(input("How old are you? "));
>>>>             >>>>
>>>>             >>>> Then again, having a prompt string is nice too, so
>>>>     I could imagine using the Console API with Integer.parseInt and
>>>>     Double.parseDouble, instead of Scanner.nextInt/nextDouble.
>>>>             >>>>
>>>>             >>>> But why have a third API, i.e. "input"?
>>>>             >>>>
>>>>             >>>> I think there are two feasible directions. Either
>>>>     embrace the Scanner API and next/nextInt/nextDouble/nextLine,
>>>>     or the Console API and readLine. Adding "input" into the mix is
>>>>     just clutter, and ambiguous clutter at that. At least readLine
>>>>     makes it clear that the entire line is consumed.
>>>>             >>>>
>>>>             >>>> Cheers,
>>>>             >>>>
>>>>             >>>> Cay
>>>>             >>>>
>>>>             >>>> --
>>>>             >>>>
>>>>             >>>> Cay S. Horstmann |
>>>>     https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IuXZk_tqIH8rEw1bD3uYb8UcIZF-nnoeFT3UG17pMO5EVXIYVRaAKi7XCq_T02HwnAek1wuV8Wed08w$
>>>>     | mailto:cay at horstmann.com <mailto:cay at horstmann.com>
>>>>             >>>
>>>>             >>
>>>>             >
>>>>
>>>>             --
>>>>             --
>>>>
>>>>             Cay S. Horstmann |
>>>>     https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!JDq2P0DR423V62MvLF-CBrjfMSFshyy9lkQdQQPt5aEojp3WbQriYDtG-00NepYgsFay4aXHAQFHA24$
>>>>     <https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IZrLgaQxOHBjUURoC5mWbfsijev257bb4C0DMamUDpoGqS5JMACpaMKsbUNQlWcGds7fifmS9sARC6aKMHEf$>
>>>>     <https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IZrLgaQxOHBjUURoC5mWbfsijev257bb4C0DMamUDpoGqS5JMACpaMKsbUNQlWcGds7fifmS9sARC6aKMHEf$>
>>>>     | mailto:cay at horstmann.com <mailto:cay at horstmann.com>
>>>>
>>>>
>>>
>>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20240220/090cf0da/attachment-0001.htm>


More information about the amber-dev mailing list