【问题标题】:Why are getArgs and getProgName IO actions?为什么是 getArgs 和 getProgName IO 操作?
【发布时间】:2012-12-27 01:31:16
【问题描述】:

我是一个完整的新手,目前正在尝试使用“Learn You a Haskell for Great Good”学习 Haskell。我已经联系到the section explaining how to work with command line arguments,但有些事情困扰着我。

根据我的理解(和haskell.org's definition),操作旨在封装副作用。命令行参数是程序给定实例的不可变输入,那么使用getProgName :: IO String 而不是getProgName :: String 有什么意义呢?换一种说法:阻止纯函数调用getProgName 有什么意义?

更新

到目前为止,我对这个问题有一些很好的答案。我接受Don Stewart's 是最简单和简洁的,但Conal's(及其关联的blog post)绝对值得一读。

【问题讨论】:

  • 我喜欢使用的一个经验法则是,任何纯值都可以在编译时完全评估。

标签: haskell io command-line-arguments referential-transparency


【解决方案1】:

要回答这些问题,您需要一个基础: 表达式e 的类型为t 是什么意思? 您可以为getProgName 之类的原语给出任意答案,但您不必这样做。 相反,请查看表达式和类型的含义。 当e 表示的值(即e 作为数学值的含义)属于t 表示的集合时,假设表达式e 具有t 类型。

现在考虑t == String。 我们希望String 有什么意义? 同样,这里有很多选择空间。 然而,用最简单的数学定义选择有用的候选人有很多优点。 在 Haskell 中 String == [Char],所以我们真的在谈论 []Char 的含义 我所知道的最引人注目的简单候选者是[] 表示列表(序列),Char 表示字符,因此String 表示字符序列,即“字符串”。

选择String 表示字符串,现在我们可以询问getProgName :: String 是否可能。 如果是这样,那么getProgName 的含义必须是一个字符序列。 但是,没有单个字符序列可以捕捉getProgName 背后的意图。 (编辑:假设您希望 getProgName 在出现在不同名称的程序中时产生不同的字符串。) 因此,我们要么选择不同的类型,例如(但不一定)IO String,要么我们必须为String选择更复杂的含义。 后一条路线起初可能看起来很吸引人,直到您考虑到深层含义。 纯函数式(更准确地说是“外延”)编程支持实用、严格的推理,这要归功于使用简单 含义(如序列)而不是复杂含义(如来自某种环境(包括操作系统、机器执行上下文)的函数)。 p>

有关密切相关的评论和讨论,请参阅博文Notions of purity in Haskell

编辑: 我认为 Peter Landin(Haskell 的祖父)用他对“外延编程”(或“真正的函数式编程”)的定义表达了这一点,他建议将其作为对 vague 的实质性替代诸如“声明式”和“函数式”编程之类的术语。 如需参考、引用和简短评论,请参阅帖子 Is Haskell a purely functional language? 中的 this comment

【讨论】:

  • 这一切都很好,直到您考虑为什么numCapabilities 是纯粹的:stackoverflow.com/questions/11851005/…
  • 我已经考虑了这些原因。正如我在对该帖子的回答中提到的,我认为 numCapabilities :: Int 是一个错误,原因正是我在上面和博客文章中概述的原因:没有一个整数与 numCapabilities 的预期含义相对应。我没有看到任何语义基础来证明分配类型的合理性。
  • 扮演魔鬼代言人,你觉得sin函数怎么样?甚至(+)?无论您碰巧在哪个硬件上运行,这些都绑定到较晚,因此,例如,sin 在 Intel 和 AMD 之间可能会有所不同。
  • @augustss:得知 Haskell 的数字类型确实具有语言定义(独立于实现)语义,我感到既惊讶又失望。
  • @augustss 哎呀——我丢了一个字。使“类型具有语言定义的......”。
【解决方案2】:

我的理解是纯函数是纯函数,因为您可以在“代码时”对它们进行推理,而编译器可以在编译时进行推理。 IO 操作取决于运行时。 getArgs,它只在程序运行时为程序提供参数,因此是一个 IO 操作。

正如 Haskell 所说的那样:

您可以将 I/O 操作想象成一个带有小脚的盒子 进入现实世界并在那里做一些事情(比如写一些 墙上的涂鸦)并可能带回一些数据。

我们不知道小脚的盒子在代码时或编译时会带来什么。

【讨论】:

  • 使用这个“编译时间”标准,我认为程序的含义可能会从一种编译变为另一种编译。例如,可能有compilationTimeAndPlace :: String。我不想允许这样的事情,因为compilationTimeAndPlace 的预期含义不是字符串,而是更复杂的东西,比如从某种编译上下文到字符串的函数。请参阅my answer to this question 了解更多信息。
【解决方案3】:

首先,getArgs 可以在运行时更改。见withArgs

其次,getArgsgetProgName 属于一类有趣的非纯计算——它们在程序运行期间被认为是常量,然而,它们在编译时不是可用的值,它们从一个程序运行到另一个。它们没有明确的外延。

参见例如较早的discussions,其中讨论了getArgs 和浮点计算。甚至可以认为 minBound/maxBound 属于这个类。

【讨论】:

  • 谢谢你的回答 :) 顺便说一句,你的书在阅读列表中(我实际上是从它开始的,但不得不退回到更简单的东西开始)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-18
  • 2017-02-11
  • 2014-04-04
  • 1970-01-01
相关资源
最近更新 更多