Является ли h ручкой или лямбда-функцией (или обеими)? - PullRequest
0 голосов
/ 15 апреля 2019

Я смотрю на простую программу ввода-вывода из Haskell Wikibook .Конструкция, представленная на этой странице, работает отлично, но я пытаюсь понять, «как».

Функция writeChar, представленная ниже, принимает путь к файлу (в виде строки) и символ и записывает символ в файл по заданному пути.Функция использует скобка , чтобы файл правильно открывался и закрывался.Из трех вычислений, выполняемых в скобках, «вычисление для выполнения между ними» - насколько я понимаю - является лямбда-функцией, которая возвращает результат hPutChar h c.

Теперь само по себе hPutChar имеет объявление hPutChar :: Handle -> Char -> IO ().Это где я потерян.Кажется, я передаю h как дескриптор hPutChar.Я ожидал бы, что дескриптор как-то ссылается на файл, открытый как fp, но вместо этого он, кажется, рекурсивно вызывает лямбда-функцию \h.Я не понимаю, как эта лямбда-функция, вызывающая себя рекурсивно, знает, что записывает c в файл в fp.

Я бы хотела понять, почему последняя строка этой функции не должна читать (\h -> hPutChar fp c).Попытка запустить его таким образом приводит к «Не удалось сопоставить тип« [Char] »с« Ручкой », что я считаю разумным, учитывая, что hPutChar ожидает тип данных Handle, а не строку.

import Control.Exception
writeChar :: FilePath -> Char -> IO ()
writeChar fp c =
    bracket
      (openFile fp WriteMode)
      hClose
      (\h -> hPutChar h c)

Ответы [ 3 ]

5 голосов
/ 15 апреля 2019

Давайте посмотрим на тип bracket (цитируется так, как это указано в вашей ссылке на Haskell Wiki):

bracket :: IO a        -- computation to run first ("acquire resource")
        -> (a -> IO b) -- computation to run last ("release resource")
        -> (a -> IO c) -- computation to run in-between
        -> IO c

В вашем случае использования первый аргумент openFile fp WriteMode - это значение IO Handle, вычисление, которое создает дескриптор, соответствующий пути fp. Третий аргумент \h -> hPutChar h c - это функция, которая берет дескриптор и возвращает вычисление, которое записывает в него. Идея состоит в том, что функция, которую вы передаете в качестве третьего аргумента, определяет, как будет использоваться ресурс, созданный первым аргументом.

4 голосов
/ 15 апреля 2019

Здесь нет рекурсии.h действительно Handle.Если вы запрограммировали на C, грубый эквивалент - FILE.Дескриптор состоит из файлового дескриптора, буферов и всего, что нужно для выполнения операций ввода-вывода в прикрепленном файле / конвейере / терминале / что угодно.openFile берет путь, открывает запрошенный файл (или устройство) и предоставляет дескриптор, который можно использовать для манипулирования запрошенным файлом.

bracket
  (openFile fp WriteMode)
  hClose
  (\h -> hPutChar h c)

Это открывает файл для создания дескриптора.Этот дескриптор передается третьей функции, которая связывает его с h и передает его hPutChar для вывода символа.Затем в конце bracket передает дескриптор на hClose, чтобы закрыть файл.

Если исключений не было, вы можете реализовать bracket следующим образом:

bracket
  :: IO resource
  -> (resource -> IO x)
  -> (resource -> IO a)
  -> IO a
bracket first last middle = do
  resource <- first
  result <- middle resource
  last resource
  pure result

Но bracket на самом деле должен установить обработчик исключений, чтобы выдержать вызов last, даже если возникает исключение.

3 голосов
/ 15 апреля 2019
hPutChar :: Handle -> Char -> IO ()

- это чистая функция Haskell, которая, учитывая два аргумента h :: Handle и c :: Char, создает чистое значение Haskell типа IO (), "IO action":

         h :: Handle      c :: Char
---------------------------------------------
hPutChr  h                c          :: IO ()

Это "действие" является просто значением Haskell, но когда оно появляется внутри блока IO monad do в main, оно становится выполненным системой времени исполнения Haskell и затем фактически выполняет операцию ввода / вывода, помещая символ c в объект файловой системы, указанный дескриптором h.

Что касается лямбда-функции, то фактический однозначный синтаксисэто

(\ h -> ... ) 

, где пробел между \ и h является необязательным, а все выражение (.......) является лямбда-выражением.Таким образом, существует no"\h entity":

  • (\ ... -> ... ) - синтаксис лямбда-выражения;
  • h в \ h -> - это параметр лямбда-функции,
  • ... в (\ h -> ... ) - это тело лямбда-функции.

bracketвызывает лямбда-функцию (\h -> hPutChar h c) с результатом, полученным в результате вычисления (openFile fp WriteMode) I / O , которое является дескриптором имени файла, на который ссылается fp, открытого в соответствии с режимом WriteMode.

Главное, что нужно понять о монадическом вводе-выводе в Haskell, это то, что «вычисление» - это не функция: это фактическое (здесь, ввод / вывод) вычисление, выполняющее фактическое открытие файла - которое создает дескриптор -которая затем используется системой времени выполнения для вызова с ней чистой функции Haskell (\ h -> ...).

Этот слой (чистого "мира" Хаскелла и нечистого "мира" ввода / вывода) является сущностью .... да, Монады .Вычисление ввода / вывода что-то делает, находит какое-то значение, использует его для вызова чистой функции Haskell, которая создает новое вычисление, которое затем запускается, передает его результаты в следующую чистую функцию и т. Д. И т. Д. И т. Д.

Так что мы сохраняем нашу чистоту в Хаскеле , только говоря о нечистых вещах.Говорить не надо.

Или это?

...