我正在尝试将SML
程序中的命令行参数写入一个文件,每个文件都在一个单独的行中。如果我在命令行上运行sml main.sml a b c easy as 1 2 3
,那么所需的输出将是一个包含内容的文件:
a
b
c
easy
as
1
2
3
但是,我从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错误:语法错误:插入EQUALOP / usr / local / smlnj / bin / sml:致命错误 - 未捕获异常使用"语法错误编译"提出来了 ../编译器/解析/主/ smlfile.sml:15.24-15.46
使用此代码:
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;
我错过了什么吗?
答案 0 :(得分:3)
嵌套值声明的正确语法是:
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 [] = ()
即,
(错误)您忘记了let ... in ... end
。
(错误)您的第二个模式[]
将永远不会匹配,因为第一个模式x
更通用并匹配所有输入列表(包括空的)。因此,即使您的语法错误已得到修复,此函数也会循环,直到崩溃为止,因为您尝试获取空列表的hd
/ tl
。
(错误)当某个功能有多个匹配案例时,只有第一个匹配案例必须加上fun
,其余的必须加|
。 (你可以自由决定如何缩进。)
(错误) SML中有两种分号:一种用于分隔声明,另一种是丢弃其第一个操作数的值(但不是效果)的运算符。可以始终避免使用第一种分隔声明的方式。第二种是你试图用来链接多个表达式的表达式,每个表达式都有一个期望的(文件I / O)效果(并且相当于在一行中有一个带有多个有效声明的let表达式,如上所述)
但是......在顶层(例如在函数体中),SML无法分辨两种分号之间的区别,因为它们都可以在那里出现。毕竟,我们要避免的第一种标记函数体的结尾,而第二种标记函数体中子表达式的结束。
避免这种歧义的方法是将;
运算符包装在不允许声明的位置,例如在in
和end
之间,或在括号内。
(错误)让此函数返回null
没有意义。您可能正在考虑nil
(空列表,又名[]
),但val null : 'a list -> bool
是一个函数!实际上,为此函数设置返回值是没有意义的。如果有的话,它可能是 bool ,表明行是否写成功(在这种情况下,您可能需要处理IO异常)。最接近不返回任何内容的函数的函数是返回类型 unit 的函数(值为()
)。
(建议)您可以使用hd
/ tl
拆分列表,但也可以使用模式匹配。使用模式匹配,就像我给出的例子一样。
(建议)您可以使用分号代替val _ = ...
声明;也;这只是一个品味问题。 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 [] = ()
(建议)每次函数调用自身时都会很愚蠢,它会打开文件,追加并关闭文件。理想情况下,您只需打开和关闭文件一次:
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
(建议)由于您对列表中的每个元素执行相同的操作,因此您还可以考虑使用推广迭代的高阶函数。通常,这将是val map : ('a -> 'b) -> 'a list -> 'b list
,但由于TextIO.output
返回单元,非常相似的val app : ('a -> unit) -> 'a list -> unit
更好:
fun writeListToFile lines =
let val fd = TextIO.openAppend filePath
in List.app (fn s => TextIO.output (fd, s ^ "\n")) lines
; TextIO.closeOut fd
end
(建议)最后,您可能想要调用此函数appendListToFile
,或只是appendLines
,并将filePath
作为参数该函数,因为filePath
意味着它是一个文件,并且该函数确实为每个s
添加了换行符。名字很重要。
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