-1

I'm trying to build a standard ML program using both imperative and functional concepts, and write the contents to a file. But my while loop doesn't seem to terminate and instead prints the same value continuously.

fun writeImage image filename =

  let val ap = TextIO.openOut filename

      val (w,h) = size image

      val row = ref 0

      val stringToWrite = "";

  in

    while !row < h do

        TextIO.output(ap,"A");

        row := !row + 1;

     TextIO.closeOut ap

  end;  

If I remove the first line after the while loop, the loop terminates. But if I include TextIO.output(ap,"A");, it doesn't. Why is this the case?

oldselflearner1959
  • 633
  • 1
  • 5
  • 22
  • Been a while since I last used SML, but have you tried removing the `;` after `row := !row + 1;`? – hnefatl Jan 02 '18 at 23:33

1 Answers1

5

Let's write your program with correct indentation, and then it becomes clear where the mistake is:

...
while !row < h do
    TextIO.output(ap,"A");
row := !row + 1;
TextIO.closeOut ap
...

You loop forever because the increment is outside of the body of the loop.

You intended to write this:

...
while !row < h do (
   TextIO.output(ap,"A");
   row := !row + 1
);
TextIO.closeOut ap
...

right?

I have long made a study of how people come to make mistakes when programming. I am curious to know how you came to make this mistake. If you believed that ; binds stronger than while then why did you believe that the TextIO.closeOut ap was after the loop? Surely if your belief was that the ; binds the increment to the loop then the ; should bind it to the loop as well. Did you perhaps think that ML is a language like Python, where the looping constructs use the whitespace as a guide to the extent of the body?

What are your beliefs about ; in ML more generally? Do you think of ; as a statement terminator, as it is in C-like languages? Or do you think of it as an infix sequencing operation on side-effecting expressions?

What was your thought process here, and how could the tooling have made it easier for you to solve your problem without having to ask for help?

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • great answer! I'm generally very confused by the brackets notation in ML, rather than the `;`. I find that in many cases ML requires really explicit information from the programmer in order to guide it, which is my mistake in assuming that it doesn't. Often times I find it quite intuitive to capture expressions like `exp(a, f a b)` in this way rather than `exp(a, (f a b))` although the former has gotten me many errors sometimes. In this case, I thought the `;` binds to the while loop, while the ones without semi-colon lies outside the while-loop. – oldselflearner1959 Jan 03 '18 at 15:05
  • More unfortunately, not sure if it's a good or bad thing, because the code worked without the brackets, it's impossible for me to tell whether it could work at first without running the code. The lack of consistency made it quite difficult for me to code intuitively unless I have lots of experience with this language. – oldselflearner1959 Jan 03 '18 at 15:06
  • Not sure what you meant by tooling, but I think it will be enormously helpful if ML can give an error that actually makes sense. For example, sometimes without using the brackets, it tells me that this certain value type does not have an applicable function, but it doesn't exactly tell me which value. And it's because I didn't use the right brackets, the compiler sees it as I'm trying to apply a function on another value type, which wasn't what I meant. Again, the lack of consistency and clarity stifles the flow of coding. – oldselflearner1959 Jan 03 '18 at 15:10
  • Regarding the semi-colon - why must it be that in the last line of the while loop, there is no semi-colon? Why must the `;` lie outside the brackets? – oldselflearner1959 Jan 03 '18 at 15:14
  • 1
    As I predicted, your confusion lies with the meaning of `;`. Think of an operator like `+`. When you say `let x = y + z in ...` it is clear that the `+` turns two expressions into one expression, meaning "evaluate both operands and sum the result as your value". `;` is *exactly the same*. It is an operator that takes two expressions, and it means "evaluate the left operand, evaluate the right operand, and use the right value as your value". Do **not** think of `;` as a "statement terminator". It is an operator on expressions, like `+`. – Eric Lippert Jan 03 '18 at 15:32
  • So, what do you want the body of the `in` to be? It's an *expression*, and you want that expression to do two things: `while x do y` and `closeout ap`. Therefore the expression you need is the sequential composition of the two: `while x do y ; closeout ap`. Now, what is `y`? You want it to do two things: `output ap` and `row := !row + 1`. So *those things have to be sequentially composed as well*. So the body of the while needs to be `output ap ; row := ...` Now that we have that it becomes clear that *we cannot tell where y ends unless we parenthesize it*. – Eric Lippert Jan 03 '18 at 15:37
  • Now do you see why the `;` *must not bind tighter than the while*? What are the consequences of the semi operator binding tighter? If it did, how would you have to write your program? – Eric Lippert Jan 03 '18 at 15:38
  • Thank you Eric for your generous help. That was very helpful, especially regarding how `;` should be seen. Yes I thought it was something like a statement terminator, and it's unexpected to realize it is actually an operator. Yes now I see why, because multiple lines are hard for the ML compiler to detect where something ends (after all, most ML code I've seen are one-liner recursions), and parantheses are certainly helpful here. – oldselflearner1959 Jan 04 '18 at 17:44
  • 1
    @oldselflearner1959: You're welcome. I would also note that in the OCaml dialect you can use "begin" and "end" instead of "(" and ")", which can make some such constructs easier to read because it makes them look more like statements. I think those are not legal in the Standard ML dialect. – Eric Lippert Jan 08 '18 at 20:41