【发布时间】:2026-02-22 00:15:02
【问题描述】:
DateTime.Now 或 Date.now 是引用透明的吗?
这是Qiita 的一篇函数式编程文章中的争议话题之一。
首先,我们必须非常小心,因为“参照透明”这个词在某种意义上是一个棘手的词/概念,并且在
What is referential transparency?
提问者说:
引用透明度一词是什么意思?我听说它被描述为“这意味着你可以用 equals 替换 equals”,但这似乎是一个不充分的解释。
一个非常典型的解释,但通常会导致我们误解的想法如下:(@Brian R. Bondy 对上述页面的#2 回答)
引用透明是函数式编程中常用的一个术语,表示给定一个函数和一个输入值,您将始终收到相同的输出。也就是说函数中没有使用外部状态。
我一直听到并认为错误的典型说法是这样的:
在编程语言中,Date.now总是返回一个对应当前时间的不同值,并根据
给定一个函数和一个输入值,您将始终收到相同的输出。
因此,Date.now 不是引用透明的!
我知道一些(函数式)程序员坚信上述说法是值得信赖的,但是,@Uday Reddy 的#1 和#3 回答解释如下:
在不了解 L 值、R 值和填充命令式程序员概念世界的其他复杂对象之间的区别的情况下谈论“引用透明性”基本上是错误的。
函数式程序员的引用透明性概念似乎在三个方面与标准概念不同:
哲学家/逻辑学家使用诸如“引用”、“外延”、“指定”和“bedeutung”(弗雷格的德语术语)之类的术语,而函数式程序员使用术语“值”。 (这不完全是他们做的。我注意到,Landin、Strachey 和他们的后代也用“价值”这个词来谈论指称/外延。这可能只是 Landin 和 Strachey 引入的术语简化,但它似乎做了一个以天真的方式使用时会有很大的不同。)
函数式程序员似乎相信这些“价值”存在于编程语言内部,而不是外部。在这方面,他们不同于哲学家和编程语言语义学家。
他们似乎认为这些“值”应该是通过评估获得的。
想一想,“外部状态”也是一个棘手的词/概念。
引用透明是函数式编程中常用的一个术语,表示给定一个函数和一个输入值,您将始终收到相同的输出。也就是说函数中没有使用外部状态。
“当前时间”是“外部状态”还是“外部值”?
如果我们称“当前时间”为“外部状态”,那么“鼠标事件”呢??
“鼠标事件”不是应该由编程上下文管理的状态,而是一个外部事件。
给定一个函数和一个输入值,您将始终收到相同的输出。
所以,我们可以这样理解:
“当前时间”既不是“输入值”也不是“外部值”也不是“外部状态”,Date.now 始终返回与正在进行的事件“当前时间”相对应的相同输出。
如果有人仍然坚持或想将“当前时间”称为“值”,再次,
- 函数式程序员似乎相信这些“价值”存在于编程语言内部,而不是外部。在这方面,他们不同于哲学家和编程语言语义学家。
“当前时间”的值从不存在于编程语言内部,而只存在于外部,而外部“当前时间”的值显然通过不是通过编程上下文而是通过真实-世界的时间流。
因此,我理解 Date.now 是参考透明的。
我想看看你的想法。谢谢。
EDIT1
在 What is (functional) reactive programming?
Conal Elliott @Conal 还解释了功能响应式编程 (FRP)。
他是最早开发FRP的人之一,他是这样解释的:
FRP 是关于 - “代表‘随时间’变化的值的数据类型”
动态/不断变化的值(即“随时间变化”的值)本身就是一流的值。
从 FRP 的角度来看,
Date可以被视为“随时间变化”的一流值,它是时间轴上的不可变对象。-
.now是在Date中处理“当前时间”的属性/函数因此,
Date.time返回代表我们“当前时间”的不可变和引用透明值。
EDIT2
(在 JavaScript 中)
引用不透明函数
let a = 1;
let f = () => (a);
Function:f 的输入为无;
Function:f 的输出取决于 a 取决于 f 之外的上下文
参照透明功能
let t = Date.now();
let f = (Date) => (Date.now());
虽然Date 值 存在于我们的物理世界中,但Date 可以被视为“随着时间的推移”不可变的 FRP 一流值。
由于任何编程上下文中引用的Date 都是相同的,我们通常可以隐式省略Date 作为输入值并简单地喜欢
let f = () => (Date.now());
EDIT3
实际上,我通过电子邮件发送给 Conal Elliott @Conal,他是 FRP 最早的开发者之一。 他很友好地回答并告诉我这里有一个类似的问题。
How can a time function exist in functional programming?
提问者说:
所以我的问题是:函数式编程中可以存在时间函数(返回当前时间)吗?
如果是,那它怎么会存在呢?不违反函数式编程的原则吗?它特别违反了引用透明性,这是函数式编程的属性之一(如果我正确理解的话)。
如果不是,那么在函数式编程中如何知道当前时间?
Conal Elliott @Conal 在 * 中的回答:
是的,如果将时间作为参数给出,纯函数可以返回时间。不同的时间论证,不同的时间结果。然后也形成其他时间函数,并将它们与函数(时间)转换(高阶)函数的简单词汇结合起来。由于该方法是无状态的,因此这里的时间可以是连续的(与分辨率无关)而不是离散的,从而大大提高了模块化。这种直觉是函数响应式编程 (FRP) 的基础。
编辑4 感谢@Roman Sausarnes 的回答。
请允许我介绍一下我对函数式编程和 FRP 的看法。
首先,我认为编程本质上是关于 数学和函数式编程追求这方面。在 另一方面,命令式编程是一种描述 机器操作,那不一定是数学。
像 Haskel 这样的纯函数式编程有些难处理 “状态”或IO,我认为整个问题来自“时间”。
“状态”或“时间”对我们人类来说几乎是主观的实体。我们 自然相信“时间”在流动或流逝,而“状态”是 变化,即Naïve realism。
我认为“时间”的朴素现实主义是根本的危险和理由 编程社区中的所有混乱,很少有人讨论这个 方面。在现代物理学中,甚至在牛顿物理学中,我们对待时间 以纯数学的方式,所以如果我们以物理学的方式概述我们的世界, 用纯粹的数学来对待我们的世界应该没什么难的 函数式编程。
所以,我概述了我们的世界/宇宙是不可变的,就像预先录制的 DVD, 而且只有我们的主观看法是可变的,包括“时间”或“状态”。
在编程中,不变的宇宙与 我们可变的主观体验就是“事件”。纯函数式 Haskell等编程语言,基本就没有这个观点, 虽然包括 Cornel Elliott 在内的一些有见地的研究人员进行了 FRP,但大多数人仍然认为 FRP 方法仍然很小或难以使用,并且其中许多人将可变状态视为理所当然。
当然,FRP 是唯一明智的解决方案,尤其是 Cornel Elliott 作为创始人应用这种哲学观点并声明 - 首先 类值“随着时间的推移”。也许,不幸的是,许多程序员 无法理解他的真正意思,因为他们被 Naïve 困住了 现实主义,并且发现很难从哲学上看待“时间”,或者 物理上不可变的实体。
所以,如果他们讨论“纯函数式”或“参照透明度” 为了数学完整性/一致性的优势,对我来说, “Date.now”在纯函数中自然是透明的 编程,仅仅因为“Date.time”访问某个点 不可变宇宙的不可变时间线。
那么像@Reddy 或@Roman Sausarnes 这样的指称语义中的引用透明度 讨论如何呢?
我概述了 FP 中的引用透明性,尤其是在 Haskell 社区中,所有这些都是关于数学完整性/一致性的。
当然,也许我可以遵循 Haskell 社区对“引用透明”的更新定义,实际上,如果我们判断代码不是引用透明的,我们就判断代码在数学上不一致,对吗?
其实,又来了,
How can a time function exist in functional programming?
一位程序员被质疑如下:
所以我的问题是:函数式编程中可以存在时间函数(返回当前时间)吗?
如果是,那它怎么会存在呢?不违反函数式编程的原则吗?它特别违反了引用透明性,这是函数式编程的属性之一(如果我正确理解的话)。
如果不是,那么在函数式编程中如何知道当前时间?
共识
违反函数式编程原则
= 违反了引用透明性,这是函数式编程的属性之一
= 数学上不一致!!
这是我们的普遍看法,对吗?
在这个问题中,很多人回答说“函数返回当前时间”不是引用透明的,尤其是在 Haskell 社区对“引用透明”的定义中,很多人提到它是关于数学一致性的。
然而,只有少数人回答“返回当前时间的函数”是参考透明的,其中一个答案是 Conal Elliott @Conal 从 FRP 的角度。
IMO,FRP,将时间流作为“随时间”处理的一流不可变值的观点是一种正确的方式,具有数学原理,如我上面提到的物理学。
那么“Date.now”/“返回当前时间的函数”如何在 Haskell 上下文中变得不透明?
嗯,我能想到的唯一解释是 Haskell 社区对“引用透明度”的更新定义有些错误。
事件驱动和数学完整性/一致性
我提到过 - 在编程中,不变的宇宙与 我们可变的主观体验是“事件”或“事件驱动”。
函数式编程以事件驱动的方式进行评估,另一方面,命令式编程通过以下步骤/例程进行评估 机器操作在代码中描述。
“Date.now”依赖于“event”,原则上,“event”对于代码的上下文是未知的。
那么,事件驱动会破坏数学的完整性/一致性吗?绝对不是。
将语法映射到含义 - 索引(食指)
C.S. Peirce introduced the term ‘indexical’ to suggest the idea of pointing (as in ‘index finger’)。 ⟦我⟧,[[这里]],[[现在]]等……
这可能是 Haskell 中“Monad”、“函子”事物在数学上相同的概念。即使在 Haskell 中,在指称语义中,[[now]] 作为“食指”也是很清楚的。
索引(食指)是主观的,事件驱动也是如此
[[I]] ,[[here]],[[now]] 等是主观的,同样,在编程中,不变的客观宇宙与 我们可变的主观体验是“事件”或“事件驱动”
因此,只要 [[now]] 绑定到“事件驱动”编程的事件声明,我认为就不会发生主观(上下文相关)数学不一致。
编辑5
@Bergi 给了我一个很好的评论:
是的,
Date.now,外部值,是引用透明的。它总是意味着“当前时间”。但
Date.now()不是,它是一个函数调用,根据外部状态返回不同的数字。引用透明的“当前时间概念”的问题在于我们无法用它计算任何东西。@KenOKABE:好像和
Date.now()一样。 问题是它不是同时表示当前时间,而是在不同的时间 - 一个程序需要时间来执行,这就是它不纯的原因。当然,我们可以设计一个引用透明的
Date.now函数/getter,它总是返回程序的启动时间(就好像程序立即执行一样),但这不是Date.now()/Date.Now的工作方式。它们取决于程序的执行状态。 ——贝尔吉
我认为我们需要讨论一下。
Date.now,外部值,是引用透明的。
[[Date.now]] 就像我在#Edit4 中提到的那样,是一个主观的索引(食指),但只要它保持在索引域中(没有执行/评估),它就是参考透明,我们同意。
但是,@Bergi 建议 Date.now()(带有执行/评估)在不同的时间返回“不同的值”,并且不再具有引用透明性。我们尚未达成一致。
我认为这个问题他肯定已经表现出来了,但只存在于命令式编程中:
console.log(Date.now()); //some numeric for 2016/05/18 xx:xx:xx ....
console.log(Date.now()); //different numeric for 2016/05/18 xx:xx:xx ....
在这种情况下,Date.now() 不是引用透明的,我同意。
然而,在函数式编程/声明式编程范式中,我们永远不会像上面那样写。我们必须这样写:
const f = () => (Date.now());
而且,这个 f 在某些“事件驱动”上下文中进行评估。这就是函数式编程代码的行为方式。
是的,此代码与相同
const f = Date.now;
因此,在函数式编程/声明式编程范式中,Date.now 或 Date.now()(带有执行/评估)在不同时间返回“不同值”永远不会有问题。
所以,正如我在 EDIT4 中提到的,只要 [[now]] 绑定到“事件驱动”编程的事件声明,主观(上下文相关)数学不一致永远不会发生了,我想。
【问题讨论】:
-
简单回答:如果在 Haskell 中,
Date.now在 IO Monad 中,则它不是引用透明的。 ;) 实时时钟是一个硬件和一个由该函数读取的寄存器。因此,它是 IO 和世界状态的获得和观察值。为什么?因为功能性程序已经脱离了它们的物理形式,也就是硬件(这是一种已知的精神疾病)。 -
感谢您的评论 :),是的,我知道 Haskell
Date.now是这样分类的,*.com/questions/210835/… 中的文章评论也有些批评 - 最喜欢的语法和流行词像“单子”-。函数式编程真的与物理形式无关吗?尤其是玻璃钢.. -
你能给我一个不透明引用的函数的例子,并解释原因吗?
-
@ycsun 我不知道你在问什么,但我添加了 Edit2。谢谢。
-
因为 JS 中的
Date.now不是getCurrentTime,而是unsafePerformIO getCurrentTime。当它确实使用未明确建模的外部状态(上下文)时,它是不透明的。
标签: functional-programming computer-science frp