Функция возвращает параметр 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 "s c). Это основной пример использования монад.

    И вместо случая на 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
    
Другие вопросы по тегам