gpt4 book ai didi

oop - 函数式编程语言是如何工作的?

转载 作者:行者123 更新时间:2023-12-03 04:59:43 26 4
gpt4 key购买 nike

如果函数式编程语言无法保存任何状态,那么它们如何执行诸如读取用户输入之类的简单操作?他们如何“存储”输入(或为此存储任何数据?)

例如:这个简单的 C 语言如何转换为 Haskell 之类的函数式编程语言?

#include<stdio.h>
int main() {
int no;
scanf("%d",&no);
return 0;
}

(我的问题受到这篇优秀文章的启发: "Execution in the Kingdom of Nouns"。阅读它让我更好地理解了面向对象编程到底是什么,Java 如何以一种极端的方式实现它,以及函数式编程语言如何形成对比。)

最佳答案

If functional programming languages cannot save any state, how do they do some simple stuff like reading input from a user (I mean how do they "store" it), or storing any data for that matter?



正如您所收集的,函数式编程没有状态——但这并不意味着它不能存储数据。不同之处在于,如果我按照以下方式编写(Haskell)语句
let x = func value 3.14 20 "random"
in ...

我保证 x 的值在 ... 中始终相同: 没有什么可以改变它。同样,如果我有一个函数 f :: String -> Integer (一个接受字符串并返回整数的函数),我可以确定 f不会修改其参数,或更改任何全局变量,或将数据写入文件等。正如 sepp2k 在上面的评论中所说,这种非可变性对于程序的推理非常有帮助:您编写折叠、旋转和破坏数据的函数,返回新副本以便您可以将它们链接在一起,并且您可以确保没有其中的函数调用可以做任何“有害”的事情。你知道 x总是 x ,而且您不必担心有人写了 x := foo bar介于 x 的声明之间以及它的用途,因为那是不可能的。

现在,如果我想读取用户的输入怎么办?正如 KennyTM 所说,这个想法是不纯函数是一个纯函数,它将整个世界作为参数传递,并返回其结果和世界。当然,您实际上并不想这样做:一方面,它非常笨重,另一方面,如果我重用同一个世界对象会发生什么?所以这以某种方式被抽象了。 Haskell 使用 IO 类型处理它:
main :: IO ()
main = do str <- getLine
let no = fst . head $ reads str :: Integer
...

这告诉我们 main是一个不返回任何内容的 IO 操作;执行这个 Action 就是运行 Haskell 程序的意思。规则是 IO 类型永远不能逃避 IO 操作;在这种情况下,我们使用 do 引入该操作。 .因此, getLine返回 IO String ,可以从两个方面来考虑:第一,作为一个 Action ,当运行时,产生一个字符串;其次,作为一个被 IO “污染”的字符串,因为它是不纯的获得的。第一个更正确,但第二个可能更有帮助。 <-需要 String出了 IO String并将其存储在 str ——但由于我们在一个 IO Action 中,我们必须把它包装起来,所以它不能“逃脱”。下一行尝试读取一个整数( reads )并获取第一个成功匹配( fst . head );这都是纯粹的(没有 IO),所以我们给它一个名字 let no = ... .然后我们可以同时使用 nostr... .因此,我们存储了不纯数据(从 getLinestr )和纯数据( let no = ... )。

这种处理 IO 的机制非常强大:它可以让您将程序的纯算法部分与不纯的用户交互部分分开,并在类型级别强制执行此操作。您的 minimumSpanningTree函数不可能改变你代码中其他地方的东西,或者给你的用户写一条消息,等等。这是安全的。

这就是在 Haskell 中使用 IO 所需要知道的全部内容;如果这就是你想要的,你可以停在这里。但是,如果您想了解为什么会这样,请继续阅读。 (请注意,这些内容将特定于 Haskell——其他语言可能会选择不同的实现。)

所以这可能看起来有点像作弊,不知何故给纯 Haskell 添加了杂质。但事实并非如此——事实证明,我们可以完全在纯 Haskell 中实现 IO 类型(只要我们获得了 RealWorld )。这个想法是这样的:一个 IO 操作 IO type与函数 RealWorld -> (type, RealWorld) 相同,它接受现实世界并返回 type 类型的对象和修改后的 RealWorld .然后我们定义了几个函数,这样我们就可以使用这种类型而不会发疯:
return :: a -> IO a
return a = \rw -> (a,rw)

(>>=) :: IO a -> (a -> IO b) -> IO b
ioa >>= fn = \rw -> let (a,rw') = ioa rw in fn a rw'

第一个允许我们谈论什么都不做的 IO 操作: return 3是一个 IO 操作,它不查询现实世界,只返回 3 . >>=运算符,发音为“bind”,允许我们运行 IO 操作。它从 IO Action 中提取值,通过函数传递它和现实世界,并返回结果 IO Action 。请注意 >>=强制执行我们的规则,即永远不允许 IO 操作的结果逃逸。

然后我们就可以翻上面的 main分为以下普通的一组功能应用:
main = getLine >>= \str -> let no = (fst . head $ reads str :: Integer) in ...

Haskell 运行时快速启动 main与初始 RealWorld ,我们准备好了!一切都是纯粹的,它只是有一个花哨的语法。

[ 编辑: As @Conal points out ,这实际上并不是 Haskell 用来做 IO 的。如果您添加并发性,或者实际上在 IO 操作中间发生任何改变世界的方式,则此模型会中断,因此 Haskell 不可能使用此模型。它仅对顺序计算是准确的。因此,Haskell 的 IO 可能有点躲闪;即使不是,它也肯定不是那么优雅。根据@Conal 的观察,看看 Simon Peyton-Jones 在 Tackling the Awkward Squad [pdf] 中所说的话,第 3.1 节;他沿着这些路线提出了可能相当于替代模型的东西,但随后因其复杂性而放弃它并采取不同的策略。]

同样,这(几乎)解释了 IO 和一般的可变性如何在 Haskell 中工作;如果这就是你想知道的全部,你可以停止阅读这里。如果你想要最后一剂理论,请继续阅读——但请记住,在这一点上,我们已经离你的问题很远了!

所以最后一件事:结果是这个结构——一个参数类型,带有 return>>= ——很一般;它被称为 monad,还有 do符号, return , 和 >>=与他们中的任何一个一起工作。正如你在这里看到的,monad 并不神奇。神奇的是 do块变成函数调用。 RealWorld类型是我们看到任何魔法的唯一地方。类型如 [] ,列表构造函数,也是 monad,它们与不纯代码无关。

您现在(几乎)知道关于 monad 概念的一切(除了一些必须满足的定律和正式的数学定义),但您缺乏直觉。网上有很多可笑的 monad 教程;我喜欢 this one ,但你有选择。然而, this probably won't help you ;获得直觉的唯一真正方法是结合使用它们并在适当的时间阅读一些教程。

但是,您不需要那种直觉来理解 IO。全面了解 monad 是锦上添花,但您现在可以使用 IO。我给你看第一个之后你就可以使用它 main功能。您甚至可以将 IO 代码视为不纯语言!但请记住,有一个潜在的功能表示:没有人在作弊。

(PS:抱歉篇幅太长。我走的有点远。)

关于oop - 函数式编程语言是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2751313/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com