将命令行参数写入 SML 文件

Write command-line arguments to file in SML

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

使用此代码:

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;

我是不是漏掉了什么?

嵌套值声明的正确语法是:

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. (Error) 当一个函数有多个匹配案例时,只有第一个必须在前面加上fun,其余的必须有一个| 代替。 (您可以自由决定如何缩进。)

  4. (Error) SML中分号有两种:一种是分隔声明,一种是丢弃值的操作符(但不是其第一个操作数的效果)。第一种分隔声明的方式总是可以避免的。第二种是您尝试使用的一种,以便链接多个表达式,每个表达式都具有所需的(文件 I/O)效果(并且等效于连续包含多个有效声明的 let 表达式,例如以上)。

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

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

  5. (错误) 有这个功能没有意义 return null。您可能在想 nil(空列表,又名 []),但 val null : 'a list -> bool 是一个函数!真的,这个函数有一个 return 值是荒谬的。如果有的话,它可能是一个 bool 指示行是否已成功写入(在这种情况下您可能需要处理 IO 异常)。最接近不 return 任何东西的函数是 return 类型 unit(值为 ())的函数。

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

  7. (建议) 您可以使用分号代替 val _ = ... 声明;还;这只是一个品味问题。例如:

    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 return 是一个 单位 ,非常相似的 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