h 是句柄还是 lambda 函数(或两者)?

Is h a handle or a lambda function (or both)?

我正在查看来自 the Haskell Wikibook 的一个简单的 IO 程序。该页面上显示的结构工作得很好,但我正在努力理解 "how"。

下面的 writeChar 函数接受一个文件路径(作为字符串)和一个字符,并将该字符写入给定路径的文件。该函数使用 bracket 来确保文件正确打开和关闭。在括号中的三个计算 运行 中,"computation to run in-between"---据我所知---是一个 lambda 函数,returns 是 hPutChar h c 的结果。

现在,hPutChar本身就有了hPutChar :: Handle -> Char -> IO ()的声明。这就是我迷路的地方。我似乎将 h 作为句柄传递给 hPutChar。我希望句柄以某种方式引用作为 fp 打开的文件,但它似乎是递归调用 lambda 函数 \h。我不明白这个递归调用自身的 lambda 函数如何知道将 c 写入 fp.

处的文件

我想明白为什么这个函数的最后一行不应该是 (\h -> hPutChar fp c)。尝试 运行 以这种方式导致 "Couldn't match type ‘[Char]’ with ‘Handle’" 鉴于 hPutChar 需要 Handle 数据类型而不是字符串,我认为这是明智的。

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

这里没有进行递归。 h确实是一个Handle。如果你用 C 编程,粗略的等价物是 FILE。句柄由文件描述符、缓冲区以及在附加的 file/pipe/terminal/whatever 上执行 I/O 所需的任何其他内容组成。 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

让我们看一下 bracket 的类型(在您的 Haskell Wiki link 中引用):

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 是一个函数,它接受一个句柄和 returns 一个写入它的计算。这个想法是,您作为第三个参数传递的函数指定将如何使用第一个参数生成的资源。

hPutChar :: Handle -> Char -> IO ()

是一个纯 Haskell 函数,给定两个参数 h :: Handlec :: Char,它产生一个 IO () 类型的纯 Haskell 值,一个“IO 操作”:

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

这个"action"只是一个Haskell值,但是当它出现在main下的IO monad do块中时,它就变成了由Haskell运行-时间系统执行然后它实际上执行了I/O放置一个字符的操作c 进入由句柄 h.

引用的文件系统实体

至于 lambda 函数,实际明确的语法是

(\ h -> ... ) 

其中\h之间的白色space是可选的,整个(.......)表达式就是lambda表达式。所以有没有\h实体”:

  • (\ ... -> ... ) 是 lambda 表达式语法;
  • h in \ h -> 是lambda函数的参数,
  • ... in (\ h -> ... ) 是 lambda 函数的主体。

bracket 使用 (openFile fp WriteMode) I/O computation 产生的结果调用 (\h -> hPutChar h c) lambda 函数,这是句柄fp引用的文件名,按照WriteMode.

模式打开

关于 Haskell monadic IO 的主要理解是 "computation" 不是函数:它是执行实际文件打开的实际(此处为 I/O)计算 - - 生成句柄 - 然后 运行-time 系统使用它来调用 纯 Haskell 函数 (\ h -> ...)

这个分层(纯Haskell"world"和不纯I/O"world")是.. .. 是的,Monad。 I/O 计算会做一些事情,找到一些值,用它来调用纯 Haskell 函数,这会创建一个新的计算,然后是 运行,提供给 its结果进入下一个纯函数等等等等

因此,我们仅通过 谈论 不纯洁的东西来保持 在 Haskell 中的纯洁。光说不做。

还是?