Hi,
I've noticed that Poly/ML handles console I/O differently than at least SML/NJ and Moscow ML.
When you read from stdIO in the interactive system, reading starts immediately after the end of the ML compiler input:
Poly/ML 5.5.0 Release
TextIO.inputLine TextIO.stdIn;Hello.
val it = SOME "Hello.\n": string option
...while in SML/NJ and Moscow ML, reading starts with the next input line:
Standard ML of New Jersey v110.74 [built: Tue Jan 31 16:23:10 2012] - TextIO.inputLine TextIO.stdIn; Hello. val it = SOME "Hello.\n" : string option
This matters especially if you are writing an interactive program which writes prompts to stdOut. Using Poly/ML, the program must do a dummy read to "consume" the rest of the compiler input before writing a prompt. This causes an incompatibility with SML/NJ and Moscow ML.
Worse, if you run a stand-alone application of the program built using Poly/ML there is no compiler input to read, so Poly/ML becomes incompatible with itself...
I feel that the reasonable behavior is that of SML/NJ and Moscow ML. Is it possible to change this in Poly/ML?
Lars-Henrik
Lars-Henrik Eriksson, PhD, Senior Lecturer Computing Science, Dept. of Information Technology, Uppsala University, Sweden E-mail: lhe@it.uu.se, Web: http://www.it.uu.se/katalog/lhe?lang=en Phone: +46 18 471 10 57, Mobile: +46 705-36 39 16, Fax: +46 18 51 19 25
On 19/09/2012 15:46, Lars-Henrik Eriksson wrote:
Hi,
I've noticed that Poly/ML handles console I/O differently than at least SML/NJ and Moscow ML.
When you read from stdIO in the interactive system, reading starts immediately after the end of the ML compiler input:
Poly/ML 5.5.0 Release
TextIO.inputLine TextIO.stdIn;Hello.
val it = SOME "Hello.\n": string option
...while in SML/NJ and Moscow ML, reading starts with the next input line:
Standard ML of New Jersey v110.74 [built: Tue Jan 31 16:23:10 2012] - TextIO.inputLine TextIO.stdIn; Hello. val it = SOME "Hello.\n" : string option
This matters especially if you are writing an interactive program which writes prompts to stdOut. Using Poly/ML, the program must do a dummy read to "consume" the rest of the compiler input before writing a prompt. This causes an incompatibility with SML/NJ and Moscow ML.
Worse, if you run a stand-alone application of the program built using Poly/ML there is no compiler input to read, so Poly/ML becomes incompatible with itself...
I feel that the reasonable behavior is that of SML/NJ and Moscow ML. Is it possible to change this in Poly/ML?
The problem is that there may be more than one ML command on a line and there has to be a consistent way to deal with it. In my view Poly/ML is consistent about this. The compiler reads exactly as many characters as it needs to form a valid ML "program" and leaves the input pointer pointing at the next character. When the "program" has completed executing the compiled code, which may consume further characters from the input, the read-eval-print loop returns to the compiler to start compiling ML. Consider the following input:
TextIO.inputLine TextIO.stdIn; 1; 2;
With Poly/ML this prints: val it = SOME " 1;\n": string option val it = 2: int
With SML/NJ it prints val it = SOME "2;\n" : string option val it = 1 : int
As far as I'm aware the Definition does not describe what should happen here.
David
19 sep 2012 kl. 18.00 skrev David Matthews:
The problem is that there may be more than one ML command on a line and there has to be a consistent way to deal with it. In my view Poly/ML is consistent about this. The compiler reads exactly as many characters as it needs to form a valid ML "program" and leaves the input pointer pointing at the next character. When the "program" has completed executing the compiled code, which may consume further characters from the input, the read-eval-print loop returns to the compiler to start compiling ML. Consider the following input:
TextIO.inputLine TextIO.stdIn; 1; 2;
With Poly/ML this prints: val it = SOME " 1;\n": string option val it = 2: int
With SML/NJ it prints val it = SOME "2;\n" : string option val it = 1 : int
As far as I'm aware the Definition does not describe what should happen here.
No, I don't think so either, but even so would be good for different ML systems to have the same behaviour.
Also the same ML code *will* behave differently when run interactively or stand-alone using Poly/ML:
Poly/ML 5.5.0 Release
fun f() = (print "Say something: "; TextIO.inputLine TextIO.stdIn; print "Thank you.\n");
val f = fn: unit -> unit
PolyML.export("test",f);
val it = (): unit
f();Hello
Say something: Thank you. val it = (): unit
unix> ./test Say something: Hello Thank you.
I believe that the stand-alone behavior is the reasonable one -- it also agrees with SML/NJ and Moscow ML.
Does this cause problems? Yes, for me it does. We use ML in the introductory programming course for CS majors and it causes unnecessary complications for the students.
Lars-Henrik
Lars-Henrik Eriksson, PhD, Senior Lecturer Computing Science, Dept. of Information Technology, Uppsala University, Sweden E-mail: lhe@it.uu.se, Web: http://www.it.uu.se/katalog/lhe?lang=en Phone: +46 18 471 10 57, Mobile: +46 705-36 39 16, Fax: +46 18 51 19 25
I don't agree with the philosophy here. There are a number of differences between Poly/ML and SML/NJ (in particular the latter's weak handling of overloading), all connected with omissions from the formal definition. Such examples could be used to inform students of how quickly incompatibilities emerge when a specification is weak. Then they are ready to see how much worse it is with C.
I don't see why that example is necessary for teaching students programming, except concerning the specific issue of the need for a formal semantics to avoid such incompatibilities.
Larry Paulson
On 20 Sep 2012, at 08:20, Lars-Henrik Eriksson lhe@it.uu.se wrote:
I believe that the stand-alone behavior is the reasonable one -- it also agrees with SML/NJ and Moscow ML.
Does this cause problems? Yes, for me it does. We use ML in the introductory programming course for CS majors and it causes unnecessary complications for the students.
On 20/09/2012 08:20, Lars-Henrik Eriksson wrote:
19 sep 2012 kl. 18.00 skrev David Matthews:
Also the same ML code *will* behave differently when run interactively or stand-alone using Poly/ML:
Poly/ML 5.5.0 Release
fun f() = (print "Say something: "; TextIO.inputLine TextIO.stdIn; print "Thank you.\n");
val f = fn: unit -> unit
PolyML.export("test",f);
val it = (): unit
f();Hello
Say something: Thank you. val it = (): unit
unix> ./test Say something: Hello Thank you.
I believe that the stand-alone behavior is the reasonable one -- it also agrees with SML/NJ and Moscow ML.
If you remember that the read-eval-print loop uses TextIO.input1 rather than TextIO.inputLine it all makes sense. At the end of compiling the next character to be read from stdIn is the character immediately after the closing semicolon. That may or may not be a newline character depending on what the rest of the input was. If the code that has just been compiled wants to read from the stream this is where it starts. The next time round the loop the compiler starts after anything that has been removed. When running as a stand-alone executable the behaviour is exactly the same; it's just that the compiler has not been run so the function starts at the beginning of the stream.
Does this cause problems? Yes, for me it does. We use ML in the introductory programming course for CS majors and it causes unnecessary complications for the students.
I am not completely wedded to the present way of doing things but I feel there has to be a stronger rationale for changing it. I would be interested to know if anyone else has feelings one way or the other.
David
On 20 Sep 2012, at 13:48, David Matthews wrote:
On 20/09/2012 08:20, Lars-Henrik Eriksson wrote:
19 sep 2012 kl. 18.00 skrev David Matthews:
Also the same ML code *will* behave differently when run interactively or stand-alone using Poly/ML:
Poly/ML 5.5.0 Release
fun f() = (print "Say something: "; TextIO.inputLine TextIO.stdIn; print "Thank you.\n");
val f = fn: unit -> unit
PolyML.export("test",f);
val it = (): unit
f();Hello
Say something: Thank you. val it = (): unit
unix> ./test Say something: Hello Thank you.
I believe that the stand-alone behavior is the reasonable one -- it also agrees with SML/NJ and Moscow ML.
If you remember that the read-eval-print loop uses TextIO.input1 rather than TextIO.inputLine it all makes sense. At the end of compiling the next character to be read from stdIn is the character immediately after the closing semicolon. That may or may not be a newline character depending on what the rest of the input was. If the code that has just been compiled wants to read from the stream this is where it starts. The next time round the loop the compiler starts after anything that has been removed. When running as a stand-alone executable the behaviour is exactly the same; it's just that the compiler has not been run so the function starts at the beginning of the stream.
Does this cause problems? Yes, for me it does. We use ML in the introductory programming course for CS majors and it causes unnecessary complications for the students.
I am not completely wedded to the present way of doing things but I feel there has to be a stronger rationale for changing it. I would be interested to know if anyone else has feelings one way or the other.
I have fairly strong feelings that it is not worth worrying about. Personally, I find the Poly/ML behaviour more to my liking than the SML/NJ behaviour because beginning execution immediately after reading the semicolon feels more intuitive. Programmers and language implementors are all doomed if the rest of the line containing the i/o command is not syntactically complete, as in:
print "Type something, please: "; val x = TextIO.inputLine TextIO.stdIn; print ( "thank you!");
(Poly/ML and SML/NJ both bind something to x and then report a syntax error, but the value of x and the syntax errors are different: in Poly/ML, the left bracket has been consumed into x and the right bracket is unmatched, but vice versa for SML/NJ.)
In any case, isn't this an issue for the implementation of the basis library rather than the compiler proper? I don't see any reason why the interactive compiler and the basis library need to share their standard i/o streams. That's what happens with the existing implementations, but I don't see that the basis library specification mandates it - couldn't a valid implementation of the basis library bring up a separate window for its standard i/o?
So I think I side with Larry Paulson in viewing this as an instructive instance where the programmer needs to distinguish between implementation-dependent behaviour and behaviour that is guaranteed by the relevant specifications or standards.
Regards,
Rob.