<!DOCTYPE html><html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<br>
<blockquote type="cite" cite="mid:CAE+3fjZyJ_RZ-Mm5Wk6D+x0opRJ64QU2eJeHtMATFH4bVGem7A@mail.gmail.com">
<div dir="ltr">
<div>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? </div>
</div>
</blockquote>
<br>
I'm willing to have that discussion! <br>
<br>
We could draw the line at "nothing", which is the status quo.
That's always an option. <br>
<br>
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. <br>
<br>
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. <br>
<br>
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.<br>
<br>
<blockquote type="cite" cite="mid:CAE+3fjZyJ_RZ-Mm5Wk6D+x0opRJ64QU2eJeHtMATFH4bVGem7A@mail.gmail.com">
<div dir="ltr">
<div>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. </div>
</div>
</blockquote>
<br>
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. <br>
<br>
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. <br>
<br>
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:<br>
<br>
if (s instanceof Integer.toString(int i)) { <br>
// use i<br>
}<br>
<br>
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. <br>
<br>
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?<br>
<br>
<br>
<br>
<blockquote type="cite" cite="mid:CAE+3fjZyJ_RZ-Mm5Wk6D+x0opRJ64QU2eJeHtMATFH4bVGem7A@mail.gmail.com">
<div dir="ltr">
<div>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?</div>
<div><br>
</div>
<div>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).</div>
<div><br>
</div>
<div>With best regards,</div>
<div>Tagir Valeev.</div>
<br>
<div class="gmail_quote">
<div dir="ltr" class="gmail_attr">On Tue, Feb 20, 2024 at
3:34 PM Brian Goetz <<a href="mailto:brian.goetz@oracle.com" moz-do-not-send="true" class="moz-txt-link-freetext">brian.goetz@oracle.com</a>>
wrote:<br>
</div>
<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div> <font size="4" face="monospace">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.<br>
<br>
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. <br>
<br>
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. <br>
<br>
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. <br>
<br>
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? <br>
<br>
<br>
<br>
<br>
</font><br>
<div>On 2/20/2024 6:44 AM, Cay Horstmann wrote:<br>
</div>
<blockquote type="cite">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. <br>
<br>
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. <br>
<br>
For my books, I need to decide what to do in the (n +
1)st edition. Should I stick with <br>
<br>
Scanner in = new Scanner(System.in); <br>
... <br>
System.out.print("How old are you? "); <br>
int age = in.nextInt(); <br>
<br>
or switch to <br>
<br>
println("How old are you?"); <br>
int age = in.nextInt(); <br>
<br>
or go all the way to <br>
<br>
int age = Integer.parseInt(input("How old are you")); <br>
<br>
I have no conceptual problem with in.nextInt(). I need
to explain method calls early on, so that students can
work with strings. <br>
<br>
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. <br>
<br>
As Brian says, there are too many conflicting goals. <br>
<br>
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. <br>
<br>
If the goal is convenience, it would be better to have
more magically statically imported methods, in
particular parseInt, parseDouble. Or readAnInt,
readADouble... <br>
<br>
Cheers, <br>
<br>
Cay <br>
<br>
<br>
On 19/02/2024 18.06, Brian Goetz wrote: <br>
<blockquote type="cite">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.") <br>
<br>
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. <br>
<br>
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! <br>
<br>
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.) <br>
<br>
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. <br>
<br>
String s = getALine(); <br>
printALine(s); <br>
<br>
is a program every student can reason about. <br>
<br>
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.) <br>
<br>
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. <br>
<br>
Remi has it absolutely right (yes, I really said that)
with <br>
<br>
<blockquote type="cite">The classical program is: <br>
input -> strings -> objects -> strings
-> output <br>
</blockquote>
<br>
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. <br>
<br>
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. <br>
<br>
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. <br>
<br>
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. <br>
<br>
<br>
On 2/19/2024 7:25 AM, Remi Forax wrote: <br>
<blockquote type="cite">I agree with Brian here, <br>
as a teacher, you have to talk about parsing and
formatting, those should not be hidden. <br>
<br>
The classical program is: <br>
input -> strings -> objects -> strings
-> output <br>
<br>
Rémi <br>
<br>
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
<br>
<br>
*From: *"Tagir Valeev" <a href="mailto:amaembo@gmail.com" target="_blank" moz-do-not-send="true"><amaembo@gmail.com></a>
<br>
*To: *"Cay Horstmann" <a href="mailto:cay@horstmann.com" target="_blank" moz-do-not-send="true"><cay@horstmann.com></a>
<br>
*Cc: *"Brian Goetz" <a href="mailto:brian.goetz@oracle.com" target="_blank" moz-do-not-send="true"><brian.goetz@oracle.com></a>,
"amber-dev" <a href="mailto:amber-dev@openjdk.org" target="_blank" moz-do-not-send="true"><amber-dev@openjdk.org></a>
<br>
*Sent: *Monday, February 19, 2024 10:09:35 AM <br>
*Subject: *Re: SimpleIO in JEP draft 8323335 <br>
<br>
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. <br>
With best regards, <br>
Tagir Valeev. <br>
<br>
On Mon, Feb 19, 2024 at 9:10 AM Cay Horstmann <a href="mailto:cay@horstmann.com" target="_blank" moz-do-not-send="true"><cay@horstmann.com></a>
wrote: <br>
<br>
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. <br>
<br>
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))? <br>
<br>
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))?
<br>
<br>
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.) <br>
<br>
On 18/02/2024 23.08, Brian Goetz wrote: <br>
> OK, so is this really just that that
you are bikeshedding the name? Renaming `input` to
`readLine`? <br>
> <br>
> This is a perfectly reasonable naming
choice, of course, but also, not what you suggested
the first time around: <br>
> <br>
> > ... "a third API" ... <br>
> <br>
> > ... "there are two feasible
directions" ... <br>
> <br>
> So what exactly are you suggesting? <br>
> <br>
> <br>
> <br>
> On 2/18/2024 5:03 PM, Cay Horstmann
wrote: <br>
>> Like I said, either the scanner
methods or the console methods are fine. <br>
>> <br>
>> 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. <br>
>> <br>
>> You don't have to "get a console".
A SimpleIO.readLine method can just invoke readLine
on the system console. <br>
>> <br>
>> 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. <br>
>> <br>
>> On 18/02/2024 22.43, Brian Goetz
wrote: <br>
>>> I think you are counting
characters and not counting concepts. <br>
>>> <br>
>>> 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.
<br>
>>> <br>
>>> 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. <br>
>>> <br>
>>> 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. <br>
>>> <br>
>>> On 2/18/2024 4:12 PM, Cay
Horstmann wrote: <br>
>>>> I would like to comment on
the simplicity of <a href="https://openjdk.org/jeps/8323335" target="_blank" moz-do-not-send="true" class="moz-txt-link-freetext">https://openjdk.org/jeps/8323335</a>
for beginning students. <br>
>>>> <br>
>>>> 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
<br>
>>>> <br>
>>>> System.out.print("How old
are you? "); <br>
>>>> int x = in.nextInt(); // in
is a Scanner <br>
>>>> <br>
>>>> than <br>
>>>> <br>
>>>> int x =
Integer.parseInt(console.readLine("How old are you?
")); <br>
>>>> <br>
>>>> or with the JEP draft: <br>
>>>> <br>
>>>> int x =
Integer.parseInt(input("How old are you? ")); <br>
>>>> <br>
>>>> 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. <br>
>>>> <br>
>>>> But why have a third API,
i.e. "input"? <br>
>>>> <br>
>>>> 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. <br>
>>>> <br>
>>>> Cheers, <br>
>>>> <br>
>>>> Cay <br>
>>>> <br>
>>>> -- <br>
>>>> <br>
>>>> Cay S. Horstmann |
<a href="https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IuXZk_tqIH8rEw1bD3uYb8UcIZF-nnoeFT3UG17pMO5EVXIYVRaAKi7XCq_T02HwnAek1wuV8Wed08w$" target="_blank" moz-do-not-send="true" class="moz-txt-link-freetext">https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IuXZk_tqIH8rEw1bD3uYb8UcIZF-nnoeFT3UG17pMO5EVXIYVRaAKi7XCq_T02HwnAek1wuV8Wed08w$</a>
| <a href="mailto:cay@horstmann.com" target="_blank" moz-do-not-send="true">mailto:cay@horstmann.com</a>
<br>
>>> <br>
>> <br>
> <br>
<br>
-- <br>
-- <br>
<br>
Cay S. Horstmann |
<a href="https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!JDq2P0DR423V62MvLF-CBrjfMSFshyy9lkQdQQPt5aEojp3WbQriYDtG-00NepYgsFay4aXHAQFHA24$" target="_blank" moz-do-not-send="true" class="moz-txt-link-freetext">https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!JDq2P0DR423V62MvLF-CBrjfMSFshyy9lkQdQQPt5aEojp3WbQriYDtG-00NepYgsFay4aXHAQFHA24$</a>
<a href="https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IZrLgaQxOHBjUURoC5mWbfsijev257bb4C0DMamUDpoGqS5JMACpaMKsbUNQlWcGds7fifmS9sARC6aKMHEf$" target="_blank" moz-do-not-send="true"><https://urldefense.com/v3/__http://horstmann.com__;!!ACWV5N9M2RV99hQ!IZrLgaQxOHBjUURoC5mWbfsijev257bb4C0DMamUDpoGqS5JMACpaMKsbUNQlWcGds7fifmS9sARC6aKMHEf$></a>
| <a href="mailto:cay@horstmann.com" target="_blank" moz-do-not-send="true">mailto:cay@horstmann.com</a>
<br>
<br>
<br>
</blockquote>
<br>
</blockquote>
<br>
</blockquote>
<br>
</div>
</blockquote>
</div>
</div>
</blockquote>
<br>
</body>
</html>