在SML

时间:2016-12-03 04:48:29

标签: sml smlnj ml

我正在尝试将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;

我错过了什么吗?

1 个答案:

答案 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 [] = ()

即,

  1. (错误)您忘记了let ... in ... end

  2. (错误)您的第二个模式[]将永远不会匹配,因为第一个模式x更通用并匹配所有输入列表(包括空的)。因此,即使您的语法错误已得到修复,此函数也会循环,直到崩溃为止,因为您尝试获取空列表的hd / tl

  3. (错误)当某个功能有多个匹配案例时,只有第一个匹配案例必须加上fun,其余的必须加|。 (你可以自由决定如何缩进。)

  4. (错误) SML中有两种分号:一种用于分隔声明,另一种是丢弃其第一个操作数的值(但不是效果)的运算符。可以始终避免使用第一种分隔声明的方式。第二种是你试图用来链接多个表达式的表达式,每个表达式都有一个期望的(文件I / O)效果(并且相当于在一行中有一个带有多个有效声明的let表达式,如上所述)

    但是......在顶层(例如在函数体中),SML无法分辨两种分号之间的区别,因为它们都可以在那里出现。毕竟,我们要避免的第一种标记函数体的结尾,而第二种标记函数体中子表达式的结束。

    避免这种歧义的方法是将;运算符包装在不允许声明的位置,例如在inend之间,或在括号内。

  5. (错误)让此函数返回null没有意义。您可能正在考虑nil(空列表,又名[]),但val null : 'a list -> bool是一个函数!实际上,为此函数设置返回值是没有意义的。如果有的话,它可能是 bool ,表明行是否写成功(在这种情况下,您可能需要处理IO异常)。最接近不返回任何内容的函数的函数是返回类型 unit 的函数(值为())。

  6. (建议)您可以使用hd / tl拆分列表,但也可以使用模式匹配。使用模式匹配,就像我给出的例子一样。

  7. (建议)您可以使用分号代替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 [] = ()
    
  8. (建议)每次函数调用自身时都会很愚蠢,它会打开文件,追加并关闭文件。理想情况下,您只需打开和关闭文件一次:

    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. (建议)由于您对列表中的每个元素执行相同的操作,因此您还可以考虑使用推广迭代的高阶函数。通常,这将是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
    
  10. (建议)最后,您可能想要调用此函数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