6

我才开始学习 Haskell。我读过它是一种纯函数式语言,其中的所有内容都是不可变的。因此,诸如输入输出、写入和读取数据库之类的事情会导致状态的可变性。我知道 Haskell 中有一个叫做 monads 的东西,它允许在 Haskell 中使用命令式特性,例如IO Monad. 但我很有趣的是,Haskell 中所有必要的事情都是在 monad 的帮助下实现的吗?在 HackageDB 上有很多包可以处理 3d 图形、数据库、解析 HTML、编写 Web 服务器等等。

这一切背后的总体思路是什么?是什么让 Haskell 保持纯粹并同时适用于编写所有这些?我希望有人会为我澄清这一点。提前致谢!

4

4 回答 4

12

我通过下面的类比来理解这些东西,我将用 JavaScript 来表达。

如何表达一个有副作用的计算?

1.一个函数

这显然是首先想到的:

var launchRockets = function () {
  prepareRockets( queryDBForPreparationParameters() )
  launchAllPreparedRockets()
  outputResults()
}

你可以看到一个有效的函数调用了一堆其他有效的函数,这些函数本身可以产生未知的效果以及随之而来的所有后果。

2. 使用说明

表达这一点的另一种方式是编写一组指令,描述这些有效的计算,以便稍后执行某些函数。(曾经编写过 SQL 查询吗?)

var launchRocketsInstructions = [
  {
    description: "Prepare rockets",
    parameters: {
      description: "Query a DB for preparation parameters"
    }
  },
  {
    description: "Launch all prepared rockets"
  },
  {
    description: "Output results"
  }
]

那么我们在第二个例子中看到了什么?我们看到一个不可变的数据树来描述计算而不是立即执行它。这里没有副作用,为了组成这个数据树,我们当然可以使用纯函数。这就是 Haskell 中副作用的本质所在。该语言提供的所有基础设施:monads、the IOdo-notation - 这些只是工具和抽象,简化了您编写单个指令树的任务。

当然,要真正执行这些指令,最终将不得不逃入副作用的狂野世界。在 JavaScript 的情况下,它类似于execute(launchRocketsInstructions),在 Haskell 的情况下,它是运行时执行您使用主模块的功能生成的指令树的根main,它成为您程序的单一入口点。因此,Haskell 的副作用实际上发生在语言范围之外,这就是为什么它是纯粹的。

于 2013-04-05T14:13:40.970 回答
8

我读过它是一种纯函数式语言,其中的所有内容都是不可变的。

Haskell 只是纯粹的/默认情况下/。如果您向编译器声明(通过一元类型)您希望具有某些效果,那么它们将被启用。

它们只是默认情况下不启用。

于 2013-04-05T13:25:31.480 回答
3

在 Haskell 中,你永远不会真正执行任何事情。您只需通过组合 IO 操作来构建您希望执行的操作的描述,然后将该描述分配给 main。然后编译器将它在主变量中找到的任何程序描述转换为可执行代码。

我建议你阅读这篇我写的 Haskell IO 介绍,它更详细地说明了事情。

但是,这仅说明了我们如何组合 IO 操作,而不是我们如何引入新操作。Haskell 有两种方式来添加新的 IO 动作:

  • 编译器内置
  • 外部函数接口 (FFI)

然后 IO monad 所做的就是将这些原始 IO 动作组合成更大的 IO 动作。

于 2013-04-05T16:22:20.410 回答
0

是的,Haskell 中所有必要的东西都是在 monad 的帮助下编写的。Monads 是允许 Haskell 既纯粹又适用于编写实际 I/O 的实用程序的一般思想。

我建议阅读 Simon Peyton Jones 的著名论文“Tackling the Awkward Squad”,它解释了如何使用 IO monad 以纯函数式语言进行实际编程。

于 2013-04-05T19:41:37.373 回答