Write command-line arguments to file in SML

独自空忆成欢 提交于 2019-12-12 04:12:22

问题


I am trying to write the command line arguments from my SML program into a file, each on a separate line. If I were to run sml main.sml a b c easy as 1 2 3 on the command line, the desired output would be to have a file with the contents:

a
b
c
easy
as
1
2
3

However, I am getting the following output from SML:

$ sml main.sml a b c easy as 1 2 3
val filePath = "/Users/Josue/Desktop/espi9890.txt" : string
val args = ["a","b","c","easy","as","1","2","3"] : string list

main.sml:4.21 Error: syntax error: inserting EQUALOP /usr/local/smlnj/bin/sml: Fatal error -- Uncaught exception Compile with "syntax error" raised at ../compiler/Parse/main/smlfile.sml:15.24-15.46

With this code:

val filePath = "/Users/Josue/Desktop/espi9890.txt";
val args = CommandLine.arguments();

fun writeListToFile x =
    val str = hd x ^ "\n";
    val fd = TextIO.openAppend filePath;
    TextIO.output (fd, str);
    TextIO.closeOut fd;
    writeListToFile (tl x);
| fun writeListToFile [] =
    null;

writeListToFile args;

Am I missing something?


回答1:


The correct syntax for nested value declarations is:

fun writeListToFile (s::ss) =
    let val fd = TextIO.openAppend filePath
        val _ = TextIO.output (fd, s ^ "\n")
        val _ = TextIO.closeOut fd
    in writeListToFile ss end
  | writeListToFile [] = ()

That is,

  1. (Error) You're forgetting the let ... in ... end.

  2. (Error) Your second pattern, [], will never match because the first one, x, is more general and matches all input lists (including the empty one). So even if your syntax error was fixed, this function would loop until it crashes because you are trying to take the hd/tl of an empty list.

  3. (Error) When a function has multiple match cases, only the first one must be prepended with fun and the rest must have a | instead. (You can decide freely how to indent this.)

  4. (Error) There are two kinds of semicolons in SML: One is for separating declarations, and one is an operator that discards the value (but not the effect) of its first operand. The first kind that separates declarations can always be avoided. The second kind is the one you are trying to employ in order to chain multiple expressions that each have a desired (file I/O) effect (and is equivalent to having a let-expressions with multiple effectful declarations in a row, like above).

    But... at the top-level (e.g. in a function body), SML is unable to tell the difference between the two kinds of semicolons, since they could both occur there. After all, the first kind that we want to avoid marks the ending of the function body while the second kind just marks the end of a sub-expression in the function body.

    The way to avoid this ambiguity is to wrap the ; operator where no declarations are allowed, e.g. between in and end, or inside a parenthesis.

  5. (Error) There is no point in having this function return null. You were probably thinking nil (the empty list, aka []), but val null : 'a list -> bool is a function! Really, it is nonsensical to have a return value for this function. If anything, it could be a bool indicating if the lines were written successfully (in which case you probably need to handle IO exceptions). The closest you get to a function that does not return anything is a function that returns the type unit (with the value ()).

  6. (Suggestion) You can use hd/tl to split the list, but you can also use pattern matching. Use pattern matching, like the examples I've given.

  7. (Suggestion) You can use semi-colons instead of the val _ = ... declarations; also; it's just a matter of taste. E.g.:

    fun writeListToFile (s::ss) =
        let val fd = TextIO.openAppend filePath
        in TextIO.output (fd, s ^ "\n")
         ; TextIO.closeOut fd
         ; writeListToFile ss
        end
      | writeListToFile [] = ()
    
  8. (Suggestion) It is rather silly that every time the function calls itself, it opens the file, appends, and closes the file. Ideally you only open and close the file once:

    fun writeListToFile lines =
        let val fd = TextIO.openAppend filePath
            fun go [] = TextIO.closeOut fd
              | go (s::ss) = ( TextIO.output (fd, s ^ "\n") ; go ss )
        in go lines end
    
  9. (Suggestion) Since you are doing the same thing to each element in a list, you may also consider using a higher-order function that generalizes the iteration. Normally, that would be a val map : ('a -> 'b) -> 'a list -> 'b list, but since TextIO.output returns a unit, the very similar val app : ('a -> unit) -> 'a list -> unit is even better:

    fun writeListToFile lines =
        let val fd = TextIO.openAppend filePath
        in List.app (fn s => TextIO.output (fd, s ^ "\n")) lines
         ; TextIO.closeOut fd
        end
    
  10. (Suggestion) Lastly, you may want to call this function appendListToFile, or simply appendLines, and take filePath as an argument to the function, since filePath implies that it is to a file, and the function does add linebreaks to each s. Names matter.

    fun appendLines filePath lines =
        let val fd = TextIO.openAppend filePath
        in List.app (fn s => TextIO.output (fd, s ^ "\n")) lines
         ; TextIO.closeOut fd
        end
    


来源:https://stackoverflow.com/questions/40944344/write-command-line-arguments-to-file-in-sml

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!