function returns TextIO.elem 选项应该是字符串选项

function returns TextIO.elem option when it should be string option

我正在编写一个函数,该函数接受文件名和读取文件时要替换的字符对列表。我目前在我的一项帮助功能上遇到错误。

prac.sml:177.5-182.12 Error: right-hand-side of clause doesn't agree with function result type [tycon mismatch]
  expression:  string option -> string * string -> unit
  result type:  TextIO.elem option -> string * string -> unit

这是给出错误的函数。我不明白到底是什么导致了这种情况发生,谁能帮我看看出了什么问题?

fun echoFile (infile)  (c) (x,y) = 
    if isSome c
      then (
        printChar (valOf c) (x,y);
        echoFile infile (TextIO.input1 infile) (x,y)
      ) else ()

这里是 printChar 函数:

fun printChar (c) (x,y)  = 
    if x = c
      then print y
      else print c

这里是调用它的函数。

fun fileSubst _ [] = ()
  | fileSubst inputFile ((x,y)::xs) =
    let
      val infile = TextIO.openIn inputFile
    in
      echoFile infile TextIO.input1(infile) (x,y);
      TextIO.closeIn(infile);
      fileSubst inputFile xs
    end

以下是对您编写的代码的一些反馈:

  • 函数 TextIO.input1 具有类型 TextIO.instream → TextIO.elem 选项 。当您 inspect the TextIO structure(例如通过在 sml 提示符中写入 open TextIO;)时,您将找到定义 type elem = char。因此,将输出视为 char 而不是 string。您可以使用 char → string 类型的函数 str。但是考虑使用行缓冲,因为一次读取一个字符的文件在系统调用和分配方面是昂贵的。

  • 我已经删除了unnecessary semicolonsfunval之后的那些声明只需要在REPL中得到立竿见影的效果。表达式之间的;是一个运算符。

  • 我删除了不必要的括号。在构造元组 ((x,y)) 和声明优先级时确实需要括号。例如,echoFile infile (TextIO.input1 infile) (x,y) 表示 echoFile 是一个具有三个参数的函数,第二个参数是 TextIO.input1 infile,它本身是一个应用于参数的函数。但是您不需要第二对括号来表示函数应用程序。也就是说,TextIO.input1 infileTextIO.input1(infile) 一样好,就像你每次有数字 42.

    都懒得写 (42) 一样

    这意味着您在fileSubst这一行中仍然存在错误:

    echoFile infile TextIO.input1(infile) (x,y)
    

    因为这被解释为 echoFile 有四个参数:infileTextIO.input1(infile)(x,y)。看起来 TextIO.input1(infile) 粘在一起,因为没有 space 间隙,但是函数应用程序被认为是函数在其参数前面的定位, 不是 括号的存在。此外,函数应用程序关联到左侧,因此如果我们在上面的行中添加显式括号,它将变为:

    (((echoFile infile) TextIO.input1) (infile)) (x,y)
    

    为了克服左结合性,我们写:

    echoFile infile (TextIO.input1 infile) (x,y)
    

    这被解释为(粗括号是明确的):

    ((echoFile infile)(TextIO.input1 infile)) (x,y)

  • 您的函数 fileSubst 似乎应该用字符 y 替换每个出现的字符 x。我可能将其称为 "file map",因为它非常类似于 (char → char) → string → string 类型的库函数 String.map。无论您保留 (x,y) 映射列表还是 char → char 函数都非常相似。

我可能会写一个函数 fileMap 类型 (char → char) → instream → outstream 类似于 String.map:

fun fileMap f inFile outFile =
    let fun go () =
            case TextIO.inputLine inFile of
                 NONE   => ()
               | SOME s => ( TextIO.output (outFile, String.map f s) ; go () )
    in go () end

然后使用它,例如喜欢:

fun cat () = fileMap (fn c => c) TextIO.stdIn TextIO.stdOut

fun fileSubst pairs =
    fileMap (fn c => case List.find (fn (x,y) => x = c) pairs of
                          NONE       => c
                        | SOME (x,y) => y)

关于这些的一些想法:

  • 当类似函数的参数可以是 filesfilenames 时,我希望区别是在变量名中更清楚。例如。 inputFileinfile 不适合我。我宁愿有例如inFilefilePath.

  • 函数是应该采用文件路径还是 instream,我想,取决于您希望如何编写它。因此,像 fileMap 这样非常通用的函数可能采用 instream / outstream,但它也可能采用文件路径。如果您正在制作这两种类型的函数,最好通过名称区分它们或将它们分成不同的模块。

  • 您可能想要处理任意 outstream,而不仅仅是 TextIO.stdOut,因为您正在处理任意 instreams 也是。您总是可以像 cat.

  • 中的特殊情况标准 input/output
  • 我在fileMap里面做了一个辅助函数go来处理递归。在这种情况下,我也可以不用 fileMap 直接调用自身:

    fun fileMap f inFile outFile =
        case TextIO.inputLine inFile of
             NONE => ()
           | SOME s => ( TextIO.output (outFile, String.map f s)
                       ; fileMap f inFile outFile )
    

    因为 fileMap 不会在附加参数中累积任何状态。但通常情况下,递归函数需要额外的参数来保持它们的状态,同时,我不想污染函数的类型签名(就像你的 echoFilec ).这是 monad 的主要用例。

    而不是 List.find 上的 case-of,我本可以使用各种库函数来处理 NONE/SOME发现于 Option:

    local
      val getOpt = Option.getOpt
      val mapOpt = Option.map
      val find = List.find
    in
      fun fileSubst pairs =
        fileMap (fn c => getOpt (mapOpt #2 (find (fn (x,y) => x = c) pairs), c))
    end