-- 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''
-- 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" }
6条答案
按热度按时间zujrkrfu1#
虽然它看起来是一个过程程序,但上面的语法被转换为一个函数程序,如下所示:
也就是说,一系列嵌套的函数有一个唯一的世界参数贯穿其中,“程序化地”对原始函数的调用进行排序。这种设计支持将 * 命令式 * 编程编码为函数式语言。
"The Awkward Squad" paper是对这种设计背后的语义决策的最好介绍,
2w3kk1z52#
我不认为我们能清楚地回答这个问题,因为“功能性”是一个模糊的概念,而且关于它的含义存在着相互矛盾的想法。所以我更喜欢彼得·兰丁建议的替代术语“外延性”,它是精确的和实质性的,对我来说,函数式编程的核心和灵魂,以及是什么使它适合等式推理。请参阅这些评论,以获得指向Landin定义的一些指针。
IO
* 不是 * 外延的。lnxxn5zx3#
这样想吧,它实际上并不“执行”IO指令,IO单子是一个纯值,封装了要完成的“命令式计算(但它实际上并没有执行)。您可以将单子(计算)以一种纯粹的方式使用单子运算符和像“do”这样的结构组合成一个更大的“计算”。尽管如此,本质上没有什么是“执行”的。事实上,在某种程度上,Haskell程序的全部目的是把一个大的“计算”放在一起,这个“计算”是它的
main
值(类型为IO a
)。pxyaymoc4#
这是一个monad。请阅读do-notation,了解封面后面的内容。
gcmastyq5#
有人能解释一下这个代码
在任何方面都是“功能性”的吗
这就是我如何看待Haskell中I/O的现状;适用一般声明〉_〈
现在(2020年6月),I/O的“功能性”如何取决于您的Haskell * 实现 *。但情况并非总是如此--事实上,Haskell * 语言 * 的原始I/O模型确实是功能性的!
是时候回到 haskell 早期的旅程了,菲利普·瓦德勒的How to Declare an Imperative帮助沿着:
(将其扩展到所有retro-Haskell I/O是留给非常热心的读者的练习;- )
这就对了:plain“ol' school“函数I/O!响应被流式传输到
main
retro_main
,然后retro_main
将请求流式传输回来:拥有所有这些经典优雅,你可以愉快地定义:
你看起来很困惑--没关系;你会找到窍门的:-D
下面是A History of Haskell第24页的一个更复杂的例子:
你还在听吗?
你旁边是垃圾桶吗?啊?你生病了?该死。
好吧,也许您会发现,使用更易于识别的 * 界面 * 会更容易:
我还包括了您的示例代码;也许这会有帮助:
是的:这仍然是简单的功能性I/O;请检查
retro_main
的类型。显然,基于对话的I/O最终就像空间站里的臭鼬一样流行。把它塞到一个单子界面里只会把臭味(及其来源)限制在空间站的一小部分--到那时,Haskellers希望那个小臭鼬消失!
于是, haskell 的抽象的输入输出接口就成了标准--这个小小的部分和它那刺鼻的主人被从空间站分离出来,带回了空气更充足的地球,空间站的大气层得到了改善,大多数 haskell 人继续做其他的事情。
但也有一些人对这种新的抽象I/O模型有一些疑问:
关于Haskell的功能性--如果模型是基于抽象的,在本例中:
IO
return
(>>=)
、catch
等getArgs
、getEnv
等那么这些实体实际上是如何被定义的,将取决于Haskell的每个实现。
所以你问题的答案是:
有人能解释一下这个代码
在任何方面都是“功能性”的吗
这取决于您使用的是哪种Haskell实现。
至于Haskell是外延的--将效果从语言移到实现中(并在算法的控制下)在过去是有效的:
[...]在我们当前的函数抽象(数字、字符串、树、函数等)的实现之下,有一些命令性机制,如内存分配和解除分配、堆栈帧修改和形实转换(thunk overwrite)(实现懒惰)。[...]
堆栈和寄存器管理以及jump/
GOTO
是语义上更简单的函数应用概念的实现。Conal Elliott.
...因此,以这种方式重新定位I/O的影响似乎也是完全合理的。
但有一个关键的区别:与使用计算机内存的其他机制不同,最简单的I/O是基于设备的,而且绝大多数I/O设备的行为 * 不 * 像计算机内存,例如,打印an SVG file后关闭计算机不会擦除纸张上的图像。
Haskell的目标是a stable foundation for real applications development--大概包括使用I/O的应用程序,并且需要它可靠地工作。未来的版本Haskell是否可以完全外延化仍然是一个研究的主题。
nhhxz33t6#
它不是函数代码。为什么会是?