Функция возвращает параметр TextIO.elem, когда он должен быть строковым параметром
Я пишу функцию, которая принимает имя файла и список пар символов, которые необходимо заменить при чтении файла. В настоящее время я получаю сообщение об ошибке в одной из моих вспомогательных функций.
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 ответ
Вот несколько отзывов к написанному вами коду:
Функция
TextIO.input1
имеет тип TextIO.instream → TextIO.elem вариант. Когда вы проверяете структуру TextIO (например, написавopen TextIO;
в приглашении sml), вы найдете определениеtype elem = char
, Поэтому относитесь к выводу как к символу, а не как к строке. Вы могли бы использовать функциюstr
типа char → string. Но рассмотрите возможность использования строковой буферизации, поскольку чтение файлов по одному символу за раз обходится дорого с точки зрения системных вызовов и распределения.Я удалил ненужные точки с запятой: те, что после
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)
держитесь вместе, потому что нет пробела, но приложение функции распознается как расположение функции перед ее аргументом, а не как наличие круглых скобок. Кроме того, приложение функции связывается слева, поэтому, если мы добавим явные скобки в строку выше, оно станет:(((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) → строка → строка. Сохраняете ли вы список (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
,Полагаю, должна ли функция использовать путь к файлу или инстрим, это зависит от того, как вы ожидаете, что хотите ее составить. Так что очень общая функция, как
fileMap
может принимать instream / outstream, но с тем же успехом - пути к файлам. Если вы выполняете оба типа функций, то, вероятно, было бы неплохо либо различить их по имени, либо разделить их на разные модули.Вы, вероятно, хотите иметь дело с произвольным опережением, а не только
TextIO.stdOut
, поскольку вы имеете дело с произвольным instream s тоже. Вы можете всегда особый случай стандартный ввод / вывод как в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
"sc
). Это основной пример использования монад.И вместо случая на
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