我正在编写一个函数,该函数接收文件名和读取文件时要替换的字符对列表。我目前在我的一个帮助功能上收到错误。
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
答案 0 :(得分:2)
以下是对您编写的代码的一些反馈:
函数TextIO.input1
的类型为 TextIO.instream→TextIO.elem选项。当您inspect the TextIO structure时(例如,通过在sml提示符下写open TextIO;
),您会找到定义type elem = char
。因此,将输出视为 char ,而不是字符串。您可以使用 char→string 类型的函数str
。但是考虑使用行缓冲,因为在系统调用和分配方面,一次读取文件一个字符很昂贵。
我已删除unnecessary semicolons:只有fun
,val
和其他声明之后才需要在REPL中获取即时结果。表达式之间的;
是一个运算符。
我删除了不必要的括号。在构造元组((x,y)
)和声明优先级时,确实需要括号。例如,echoFile infile (TextIO.input1 infile) (x,y)
表示echoFile
是一个带有三个参数的函数,第二个参数是TextIO.input1 infile
,它本身就是一个应用于参数的函数。但是,您不需要第二对括号来表示功能应用。也就是说,TextIO.input1 infile
和TextIO.input1(infile)
一样好,就像每次有(42)
时你都不愿意写42
。
这意味着此行中fileSubst
仍然存在错误:
echoFile infile TextIO.input1(infile) (x,y)
因为这被解释为echoFile
有四个参数:infile
,TextIO.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)
关于这些的一些想法:
当类似函数的参数可以是文件或文件名时,我希望变量名中的区别更明确。例如。 inputFile
与infile
对我不做。我宁愿有例如inFile
和filePath
。
我猜,函数是应该采用文件路径还是 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