有人能解释一下这段代码是怎么回事吗
do { putStrLn "ABCDE" ; putStrLn "12345" }
在任何方面都是“功能性的”吗?
这就是我对 Haskell 中 I/O 现状的看法;通常的免责声明适用>_
现在(2020 年 6 月),I/O 的“功能性”如何取决于您的 Haskell实现。但情况并非总是如此 - 事实上,Haskell 语言 的原始 I/O 模型确实是功能性的!
在 Philip Wadler 的 How to Declare an Imperative 的帮助下,是时候回到 Haskell 的早期时代了:
import Prelude hiding (IO)
import qualified Prelude (IO)
import Control.Concurrent.Chan(newChan, getChanContents, writeChan)
import Control.Monad((<=<))
-- pared-back emulation of retro-Haskell I/O
--
runDialogue :: Dialogue -> Prelude.IO ()
runDialogue d =
do ch <- newChan
l <- getChanContents ch
mapM_ (writeChan ch <=< respond) (d l)
respond :: Request -> Prelude.IO Response
respond Getq = fmap Getp getChar
respond (Putq c) = putChar c >> return Putp
main = runDialogue (retro_main :: Dialogue)
{-
implementation side
-----------------------------------
========== retro-Haskell ==========
-----------------------------------
language side
-}
-- pared-back definitions for retro-Haskell I/O
-- from page 14 of Wadler's paper
--
data Request = Getq | Putq Char
data Response = Getp Char | Putp
type Dialogue = [Response] -> [Request]
(将其扩展到所有retro-Haskell I/O 留给非常热心的读者作为练习;-)
你去吧:普通的“ol' school”功能性 I/O!响应流式传输到 main retro_main,然后将请求流式传输回来:
拥有所有经典优雅,您可以愉快地定义:
-- from page 15 of Wadler's paper
echoD :: Dialogue
echoD p =
Getq :
case p of
Getp c : p' ->
if (c == '\n') then
[]
else
Putq c :
case p' of
Putp : p'' -> echoD p''
你看起来很困惑——没关系;你会得到它的窍门:-D
这是A History of Haskell第24页的一个更复杂的例子:
{-
主要〜(成功:〜((Str userInput):〜(成功:〜(r4:_))))
= [ AppendChan stdout "输入文件名\n",
ReadChan 标准输入,
AppendChan 标准输出名称,
读取文件名,
AppendChan 标准输出
(案例 r4 的
Str 内容 -> 内容
失败 ioerr ->“无法打开文件”)
] where (name : _) = lines userInput
-}
你还在吗?
那是你旁边的垃圾桶吗?嗯?你病了?该死。
好吧 - 也许你会发现使用更易识别的界面会更容易一些:
-- from page 12 of Wadler's paper
--
echo :: IO ()
echo = getc >>= \ c ->
if (c == '\n') then
done
else
putc c >>
echo
-- from pages 3 and 7
--
puts :: String -> IO ()
puts [] = done
puts (c:s) = putc c >> puts s
done :: IO ()
done = return ()
-- based on pages 16-17
--
newtype IO a = MkIO { enact :: Reality -> (Reality, a) }
type Reality = ([Response], [Request])
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO m k = MkIO $ \ (p0, q2) -> let ((p1, q0), x) = enact m (p0, q1)
((p2, q1), y) = enact (k x) (p1, q2)
in
((p2, q0), y)
unitIO :: a -> IO a
unitIO x = MkIO $ \ w -> (w, x)
putc :: Char -> IO ()
putc c = MkIO $ \ (p0, q1) -> let q0 = Putq c : q1
Putp : p1 = p0
in
((p1, q0), ())
getc :: IO Char
getc = MkIO $ \ (p0, q1) -> let q0 = Getq : q1
Getp c : p1 = p0
in
((p1, q0), c)
mainD :: IO a -> Dialogue
mainD main = \ p0 -> let ((p1, q0), x) = enact main (p0, q1)
q1 = []
in
q0
-- making it work
instance Monad IO where
return = unitIO
(>>=) = bindIO
我还包含了您的示例代码;也许这会有所帮助:
-- local version of putStrLn
putsl :: String -> IO ()
putsl s = puts s >> putc '\n'
-- bringing it all together
retro_main :: Dialogue
retro_main = mainD $ do { putsl "ABCDE" ; putsl "12345" }
是的:这仍然是简单的函数式 I/O;检查retro_main的类型。
显然,基于对话的 I/O 最终与空间站中的臭鼬一样流行。将它塞进一个单子界面只会将恶臭(及其来源)限制在车站的一小部分 - 到那时,Haskellers 想要那个臭小子消失!
因此,Haskell 中用于 I/O 的抽象单子接口成为标准——那一小部分及其刺鼻的居住者被从空间站中分离出来并被拖回地球,那里的新鲜空气更加丰富。空间站的气氛有所改善,大多数哈斯克勒人继续做其他事情。
但有些人对这种新的、抽象的 I/O 模型感到烦恼 - was Haskell no longer functional?
好吧,如果模型是基于抽象的,在这种情况下:
- I/O 操作的抽象类型:
IO
- 用于构造简单 I/O 操作的抽象函数:
return
- 用于组合 I/O 操作的抽象函数:
(>>=)、catch 等
- 特定 I/O 操作的抽象函数:
getArgs、getEnv 等
那么这些实体的实际定义方式将特定于 Haskell 的每个实现。现在应该问的是:
所以你的问题的答案:
有人能解释一下这段代码是怎么回事吗
do { putStrLn "ABCDE" ; putStrLn "12345" }
在任何方面都是“功能性的”吗?
现在取决于您使用的 Haskell 实现。
不是您想要的答案?现在,要么是这个,要么是the skunk...