当函数应该是字符串选项时,函数返回TextIO.elem选项

时间:2018-01-16 00:35:07

标签: sml smlnj

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

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

1 个答案:

答案 0 :(得分:2)

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

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

  • 我已删除unnecessary semicolons:只有funval和其他声明之后才需要在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)粘在一起,因为没有空隙,但函数应用程序被识别为函数在其参数前面的定位, not 括号的存在。此外,函数应用程序关联到左侧,因此如果我们在上面的行中添加显式括号,它将变为:

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

    为了克服左联结,我们写道:

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

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

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

  • 您的函数fileSubst似乎应该用字符x替换每个字符y。我可能称之为"文件映射",因为它非常类似于类型的库函数String.map(char→char)→string→string 。是否保留(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)

关于这些的一些想法:

  • 当类似函数的参数可以是文件文件名时,我希望变量名中的区别更明确。例如。 inputFileinfile对我不做。我宁愿有例如inFilefilePath

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

  • 您可能想要处理任意 outstream ,而不仅仅是TextIO.stdOut,因为您正在处理任意 instream ,太。您可以像cat中一样使用特殊情况的标准输入/输出。

  • 我在go内部创建了一个辅助函数fileMap来处理递归。在这种情况下,我也可以不用,让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不会在其他参数中累积任何状态。但通常的情况是,递归函数需要额外的参数来保持其状态,同时,我不想污染函数的类型签名(就像使用echoFile& #39; s c)。这是monad的一个主要用例。

    而不是List.find上的 case-of ,我可以使用各种库函数来处理NONE / SOME中{ {1}}:

    Option