我刚刚开始看看Haskell(我之前的FP经验是在Scheme中),并且我遇到了这段代码:
do { putStrLn "ABCDE" ; putStrLn "12345" }
对我来说,这就是过程式编程,如果有的话——特别是因为副作用的连续性。
有人可以解释一下这段代码在任何方面如何“起作用”吗?
I'm just starting to take a look at Haskell (my previous FP experience is in Scheme), and I came across this code:
do { putStrLn "ABCDE" ; putStrLn "12345" }
To me, this is procedural programming, if anything -- especially because of the consecutive nature of side effects.
Would someone please explain how this code is "functional" in any respect?
发布评论
评论(6)
虽然它看起来是一个过程程序,但上述语法被转换为一个函数式程序,如下所示:
也就是说,一系列嵌套函数具有通过它们线程化的唯一世界参数,“按过程”对原始函数的调用进行排序。此设计支持将命令式编程编码为函数式语言。
对于此设计背后的语义决策的最佳介绍是 “The Awkward Squad” 论文,
While it appears to be a procedural program, the above syntax is translated into a functional program, like so:
That is, a series of nested functions that have a unique world parameter threaded through them, sequencing calls to primitive functions "procedurally". This design supports an encoding of imperative programming into a functional language.
The best introduction to the semantic decisions underlying this design is "The Awkward Squad" paper,
我认为我们无法清楚地回答这个问题,因为“功能性”是一个模糊的概念,并且对于它的含义存在着相互矛盾的想法。所以我更喜欢彼得·兰丁建议的替代术语“外延”,它是精确和实质性的,对我来说,是“心”和“外延”。函数式编程的灵魂以及它为何适合等式推理。请参阅这些评论获取一些指导根据兰丁的定义。
IO
不是指示性的。I don't think we can answer this question clearly, because "functional" is a fuzzy notion, and there are contradictory ideas out there of what it means. So I prefer Peter Landin's suggested replacement term "denotative", which is precise and substantive and, for me, the heart & soul of functional programming and what makes it good for equational reasoning. See these comments for some pointers to Landin's definition.
IO
is not denotative.这样想吧。它实际上并不“执行”IO 指令。 IO monad 是一个纯值,封装了要完成的“命令式计算”(但它实际上并不执行它)。您可以使用 monad 运算符和“do”等结构以纯粹的方式将 monad(计算)组合在一起形成更大的“计算”。尽管如此,本身并没有“执行”任何东西。事实上,在某种程度上,Haskell 程序的全部目的是将一个大的“计算”放在一起,即它的
main
值(其类型为IO a
)。当你运行程序时,运行的就是这个“计算”。Think about it this way. It doesn't actually "execute" the IO instructions. The IO monad is a pure value that encapsulates the "imperative computation" to be done (but it doesn't actually carry it out). You can put monads (computations) together into a bigger "computation" in a pure way using the monad operators and constructs like "do". Still, nothing is "executed" per se. In fact, in a way the whole purpose of a Haskell program is to put together a big "computation" that is its
main
value (which has typeIO a
). And when you run the program, it is this "computation" that is run.这是一个monad。阅读do-notation,了解发生的情况在封面后面。
This is a monad. Read about the do-notation for an explanation of what goes on behind the covers.
这就是我对 Haskell 中 I/O 当前情况的看法;通常的免责声明适用>_<
目前(2020 年 6 月),I/O 的“功能”程度取决于您的 Haskell实现。但情况并非总是如此 - 事实上,Haskell 语言的原始 I/O 模型确实是实用的!
在 Philip Wadler 的 如何声明命令:(
将其扩展到所有 复古 Haskell I/O 留给非常热衷的练习读者们;-)
好了:简单的“ol' school”函数式 I/O!响应会流式传输到
main
retro_main
,然后将请求流式传输回来:凭借所有经典的优雅,您可以愉快地定义:
您看起来很困惑 - 没关系;你会掌握它的窍门:-D
这是 第 24 页的更复杂的示例。 rep=rep1&type=pdf" rel="nofollow noreferrer">Haskell 的历史:
你还在吗?
你旁边那个是垃圾桶吗?啊?你生病了吗?该死。
好吧 - 也许您会发现使用更易于识别的界面会更容易一些:
我还包含了您的示例代码;也许这会有所帮助:
是的:这仍然是简单的功能性 I/O;检查
retro_main
的类型。显然,基于对话的 I/O 最终像空间站里的臭鼬一样受欢迎。将其填充到单子接口中只会将恶臭(及其来源)限制在空间站的一小部分 - 到那时,Haskellers 希望那个小臭东西消失!
因此,I/O 的抽象单子接口哈斯克尔的太空舱被制定为标准——那个小部分和里面散发着刺鼻气味的乘客被从空间站上分离出来并拖回地球,那里的新鲜空气更加充足。空间站上的气氛有所改善,大多数哈斯凯勒人继续做其他事情。
但有些人对这种新的、抽象的 I/O 模型有一些疑问:
关于 Haskell 的函数式 - 如果模型基于抽象,在本例中:
IO
return
(>>=)
、catch
等getEnv
等,那么这些实体的实际定义方式将特定于 Haskell 的每个实现。现在应该问的是:
所以你的问题的答案是:
现在取决于您使用的 Haskell 的实现。
至于 Haskell 是外延性的 - 将效果从语言转移到实现(并在算法的控制下)在过去是有效的:
...因此以这种方式重新定位 I/O 的效果似乎是完全合理的。
但有一个关键的区别:与使用计算机内存的其他机制不同,最简单的 I/O 是基于设备,而绝大多数 I/O 设备不 表现得像计算机的内存,例如在打印SVG 文件后关闭计算机。 t 从中删除图像 纸。
Haskell 的目的是实际应用程序开发的稳定基础 - 大概包括使用 I/O 并需要它可靠工作的应用程序。未来版本的 Haskell 是否可以完全具有外延性仍然是一个问题-598" rel="nofollow noreferrer">研究主题...
This is how I see the current situation with I/O in Haskell; the usual disclaimers apply >_<
Right now (2020 Jun), how "functional" I/O is depends on your Haskell implementation. But that wasn't always the case - in fact, the Haskell language's original model of I/O really was functional!
Time for a trip back to the early days of Haskell, helped along by Philip Wadler's How to Declare an Imperative:
(Extending it to all of retro-Haskell I/O is left as an exercise for very keen readers ;-)
There you go: plain "ol' school " functional I/O! The responses are streamed to
main
retro_main
, which then streams the requests back:With all that classic elegance, you could happily define:
You look confused - that's alright; you'll get the hang of it :-D
Here's a more-sophisticated example from page 24 of A History of Haskell:
Are you still there?
Is that a garbage bin next to you? Huh? You were ill? Darn.
Alright then - perhaps you'll find it a bit easier with a more-recognisable interface:
I've also included your sample code; maybe that'll help:
Yes: this is all still simple functional I/O; check the type of
retro_main
.Apparently, dialogue-based I/O ended up being about as popular as a skunk in a space station. Stuffing it inside a monadic interface only confined the stench (and its source) to one small section of the station - by then, Haskellers wanted that lil' stinker gone!
So the abstract monadic interface for I/O in Haskell was made the standard - that small section and its pungent occupant was detached from the space station and hauled back to Earth, where fresh air is more plentiful. The atmosphere on the space station improved, and most Haskellers went on to do other things.
But a few had some questions about this new, abstract model of I/O:
Regarding Haskell being functional - if the model is based on an abstraction, in this case:
IO
return
(>>=)
,catch
, etcgetArgs
,getEnv
, etcthen how these entities are actually defined will be specific to each implementation of Haskell. What should now be asked is this:
So the answer to your question:
now depends on which implementation of Haskell you're using.
As for Haskell being denotative - moving effects from the language into the implementation (and under the control of algorithms) has worked in the past:
...so also relocating the effects of I/O in that way seems entirely reasonable.
But there's a crucial difference: unlike those other mechanisms which use the computer's memory, the simplest of I/O is device-based and the vast majority of I/O devices do not behave like the memory of a computer e.g. turning off your computer after printing an SVG file doesn't erase the image from the paper.
Haskell was intended to be a stable foundation for real applications development - presumably that includes applications which use I/O, and need it to work reliably. Whether a future version Haskell could be made completely denotative remains a subject of study...
它不是功能代码。为什么会这样?
It isn't functional code. Why would it be?