代码之家  ›  专栏  ›  技术社区  ›  Jordan

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

  •  1
  • Jordan  · 技术社区  · 8 年前

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

    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 回复  |  直到 8 年前
        1
  •  2
  •   sshine    8 年前

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

    • 功能 TextIO.input1 具有类型 文本IO。流内文本。elem选项 . 当你 inspect the TextIO structure (例如,通过书面形式 open TextIO; 在sml提示中),您将找到定义 type elem = char . 因此,将输出视为 烧焦 而不是 一串 . 您可以使用该功能 str 的类型 字符字符串 . 但考虑使用行缓冲,因为一次读取一个字符的文件在系统调用和分配方面很昂贵。

    • 我已删除 unnecessary semicolons :后面的那些 fun , val 只有在REPL中需要其他声明才能立即得到结果。这个 ; 表达式之间是运算符。

    • 我去掉了不必要的括号。构造元组时确实需要括号( (x,y) )以及在声明优先级时。例如 echoFile infile (TextIO.input1 infile) (x,y) 他说 echoFile 是一个有三个参数的函数,第二个参数是 TextIO.input1 infile 文本IO。输入1填充 TextIO.input1(infile) ,就像你不费心写信一样 (42) 每次你有电话号码 42 .

      这意味着您的 fileSubst 在这一行:

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

      因为这被解释为 echoFile文件 有四个参数: infile , 文本IO。输入1 , (infile) (x,y) . 可能看起来 文本IO。输入1 (填充) 粘在一起是因为没有空格,但函数应用程序被认为是函数在其参数前面的定位, 括号的存在。此外,函数应用程序关联到左边,因此如果我们在上面的行中添加显式括号,它将变为:

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

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

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

      将其解释为(粗体括号为显式括号):

      ((echoFile infile) ( 文本IO。输入1填充 ) ) (x,y)

    • 看来你的功能 文件子集 应该替换角色的每次出现 x 与角色 y . 我可能会称之为“文件映射”,因为它与库函数非常相似 String.map 的类型 (char–char)–string–string . 是否保留 (x,y) 映射或 字符–字符 功能非常相似。

    我可能会写一个函数 fileMap 使用类型 (char–in-stream–out-stream) 相似 一串地图 :

    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)
    

    关于这些问题的一些想法:

    • 类似函数的参数可以是 文件夹 文件名 ,我希望在变量名称上的区别更加明确。例如。 inputFile vs。 填充 不是为我做的。我宁愿吃。 inFile filePath .

    • 函数应采用文件路径还是 河道内 ,我想,这取决于你想如何创作它。所以一个非常通用的函数 文件映射 可能需要 河道内 / 扩展 ,但它也可以采用文件路径。如果您正在创建这两种类型的函数,那么最好通过名称来区分它们,或者将它们分为不同的模块。

    • 你可能想处理武断的 扩展 s、 不仅仅是 TextIO.stdOut ,因为你处理的是武断 河道内 s、 我也是。您可以始终使用特殊情况下的标准输入/输出,如 cat .

    • 我做了一个辅助函数, go 在…内 文件映射 处理递归。在这种情况下,我也可以不用 文件映射 直接调用自身:

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

      自从 文件映射 不会在其他参数中累积任何状态。但是递归函数通常需要额外的参数来保持其状态,同时,我不想污染函数的类型签名(比如 echoFile文件 c ). 这是monad的一个主要用例。

      而不是 的情况 在…上 List.find ,我可以使用各种库函数来处理 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