2

I'm facing trouble getting this assignment to work out. We are supposed to take command line arguments as input and write them out to a file. So far I have done below code:

val args = CommandLine.arguments()

val a = length args;

val os = TextIO.openOut "rodr4719.txt";

TextIO.output(os, "rodr4719");


fun writeFile(os, args, a) = 


if a = -1 then

TextIO.output(os, "rodr4719")
TextIO.closeOut os
OS.Process.exit(OS.Process.success)

else

val str = nth (args, a);
TextIO.output(os, str ^"\n" );
a = a-1;
writeFile(os, args, a)
end;

writeFile(os, args, a-1)

and the error I am getting is

.sml:22.1 Error: syntax error: inserting EQUALOP
sshine
  • 15,635
  • 1
  • 41
  • 66

1 Answers1

0

This is somewhat of a duplicate of the following two Q&A's:

Here is some general feedback:

  • Indentation: You can make the code slightly more readable by adding indentation.

    fun writeFile (os, args, a) = 
        if a = -1 then
            TextIO.output(os, "rodr4719")
            TextIO.closeOut os
            OS.Process.exit(OS.Process.success)
        else
            val str = nth (args, a);
            TextIO.output(os, str ^"\n" );
            a = a-1;
            writeFile(os, args, a)
    end
    

    (This code is still broken for several reasons listed below.)

  • end: This keyword is not necessary when you're just declaring a fun. It occurs in several places in SML's syntax, notably as let ... in ... end, and you've probably seen it there.

  • Variable names: I'd name the input arguments differently: os sounds like operating system when it was probably an abbreviation for outstream. Just call it outstream. Some people like to call it fd for file descriptor, but it's all about the understanding of the intended audience. args could be more generic: This function can write any list of strings, not just command-line arguments. Just call it lines. a sounds like it could be any type, but it's just an integer. I'd call it n, but I'd actually not use a number to determine my stop condition.

  • Semicolons: When your function does multiple things with side-effects, you can use the ; operator. You're not doing this in the then-part, and you're doing it wrong in the else-part, admittedly because the ; operator in SML is syntactically confusing.

    The short version: The semicolons in val foo = x; val bar = y; are different than the semicolons in val foo = (TextIO.output (fd, x); TextIO.output (fd, y)).

    When you want the semicolon expression operator, wrap your semicolon'ed expression inside a parenthesis or a let-expression. Otherwise it gets interpreted as the (optional) semicolon declaration operator.

  • When iterating a list of lines, rather than keeping a counter, perform pattern matching on the list. This greatly simplifies when your recursion ends, since you're always addressing the head of the list one at a time anyways.

  • Don't exit: You probably don't want to exit the program once you're done; exiting the function should be sufficient. This way you can re-use the function in a more complex control-flow without it exiting on behalf of the entire program.

  • Open and close files in the same scope: This is general programming advice: When you're dealing with side-effects, to ensure that you don't forget to close something you've opened and that you don't close something twice, doing it in the same vicinity of the code makes it much easier for you to reason about the correctness. You could make a simple function that always deals with the opening and closing.

  • val inside function body: When you need a temporary value binding inside a function body or some expression, use a let-expression. Incidentally, someone just asked Why do I use `let` and not just `val` to declare a variable inside a function in SML? today.

  • Immutable values: In SML you can't update a value like a = a-1. a isn't a variable, but a value binding. You can re-bind it to a new value that is based on the old one, thus shadowing the old one, but this isn't necessary. Instead, perform a recursive call with the updated value.

I've modified your program with those things in mind:

fun writeFile (outstream, []) = ()
  | writeFile (outstream, line::lines) =
    ( TextIO.output (outstream, line ^ "\n") ; writeFile (outstream, lines) )

(* Demo *)
val myFile = TextIO.openOut "rodr4719.txt"
val _ = writeFile (myFile, CommandLine.arguments ())
val _ = TextIO.closeOut myFile

(The val _ = ...s are a fancy way of evaluating expressions at the top-level without having expressions directly at the top level. In the interactive REPL foo(); on its own line means val it = foo();, but once you compile your programs to produce stand-alone binary executables, this no longer works. val _ = ... basically throws away the result of the computation after performing it, and since both writeFile and TextIO.closeOut return (), they're not worth binding to anything.)

sshine
  • 15,635
  • 1
  • 41
  • 66
  • Wow, thank you so much. I'm sorry. In like 6 years of using Stack Overflow this is the first question I have asked. I'll keep all those points in mind. With your corrections, it's now giving me an "Error: syntax error: inserting RPAREN" on line 3 of your modified code. I searched stack overflow and can't seem to find an explanation of this error. What is the error complaining about? – Lennin Rodriguez May 04 '18 at 15:32
  • Oh, and the integer literal for negative one is `~1`, not `-1`. – sshine May 04 '18 at 19:20