问题
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,
(Error) You're forgetting the
let ... in ... end
.(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 thehd
/tl
of an empty list.(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.)(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. betweenin
andend
, or inside a parenthesis.(Error) There is no point in having this function return
null
. You were probably thinkingnil
(the empty list, aka[]
), butval 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()
).(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.(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 [] = ()
(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
(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 sinceTextIO.output
returns a unit, the very similarval 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
(Suggestion) Lastly, you may want to call this function
appendListToFile
, or simplyappendLines
, and takefilePath
as an argument to the function, sincefilePath
implies that it is to a file, and the function does add linebreaks to eachs
. 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