【问题标题】:Is Date.now referential transparent?Date.now 引用透明吗?
【发布时间】:2026-02-22 00:15:02
【问题描述】:

DateTime.NowDate.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.nowDate.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


【解决方案1】:

好的,我要试一试。我不是这方面的专家,但我花了一些时间思考你链接到的@UdayReddy's answers to this question,我想我已经把它缠住了。

分析哲学中的参照透明

我认为你必须从雷迪先生回答另一个问题时所做的开始。雷迪先生写道:

“所指”一词在分析哲学中用于谈论表达式所指的事物。与我们在编程语言语义中所说的“意义”或“外延”大致相同。

注意“外延”一词的使用。编程语言有句法或文法,但它们也有语义或意义。 Denotational semantics 是将语言的句法翻译成其数学含义的实践。

据我所知,指称语义并未被广泛理解,尽管它是用于理解、设计和推理计算机程序的最强大的工具之一。我需要花一点时间来为您的问题的答案奠定基础。

指称语义:将语法映射到含义

指称语义背后的思想是计算机语言中的每个句法元素都有相应的数学意义或语义。指称语义是句法和语义之间的显式映射。取句法数字1。您可以将其映射到其数学含义,即数学数字1。语义函数可能如下所示:

syntax
   ↓
  ⟦1⟧ ∷ One
        ↑ 
    semantics

有时双方括号用于表示“意义”,在这种情况下,语义方面的数字1 拼写为One。这些只是指示我们何时谈论语义和何时谈论语法的工具。您可以将该函数理解为“句法符号1 的含义是数字One。”

我上面使用的例子看起来很简单。当然1 表示One。还有什么意思?然而,它不必。你可以这样做:

⟦1⟧ ∷ Four

那将是愚蠢的,没有人会使用这种愚蠢的语言,但它仍然是一种有效的语言。但关键是指称语义允许我们明确我们编写的程序的数学含义。以下是使用 lambda 表示法对整数 x 进行平方的函数的表示:

⟦square x⟧ ∷ λx → x²

现在我们可以继续讨论引用透明度了。

参照透明就是意义

请允许我再次附上乌代先生的回答。他写道:

如果用引用同一实体的另一个术语替换该上下文中的术语不会改变含义,则句子中的上下文是“引用透明的”。

将其与您向普通程序员询问引用透明性意味着什么时得到的答案进行比较。他们通常会说类似于您上面引用的答案:

引用透明是函数式编程中常用的一个术语,表示给定一个函数和一个输入值,您将始终收到相同的输出。也就是说函数中没有使用外部状态。

该答案在值和副作用方面定义了引用透明度,但它完全忽略了意义。

这是第二个定义下不透明的函数:

var x = 0

func changeX() -> Int {
    x += 1
    return x
}

它读取一些外部状态,对其进行变异,然后返回值。它不需要输入,每次调用它时都会返回不同的值,并且它依赖于外部状态。嗯。大不了。

给定一个正确的指称语义,它仍然是引用透明的。

为什么?因为您可以将其替换为另一个具有相同语义含义的表达式

现在,该函数的语义更加混乱。我不知道如何定义它。它与状态转换有关,给定状态 s 和产生新状态 s' 的函数,表示可能看起来像这样,尽管我不知道这在数学上是否正确:

⟦changeX⟧ ∷ λs → (s → s')

对吗?我没有头绪。 Strachey 弄清楚了命令式语言的指称语义,但它很复杂,我还不明白。然而,通过建立指称语义,他确立了命令式语言与函数式语言一样具有引用透明性。为什么?因为可以精确地描述数学含义。一旦您知道某事物的精确数学含义,您就可以将其替换为具有相同含义的任何其他术语。因此,即使我不知道 changeX 函数的真正语义是什么,但我知道如果我有另一个具有相同语义含义的术语,我可以将一个替换为另一个。

那么Date.now呢?

我对那个函数一无所知。我什至不确定它来自什么语言,尽管我怀疑它可能是 Javascript。但谁在乎。它的指称语义是什么? 是什么意思?在不改变程序含义的情况下,您可以在其位置插入什么?

丑陋的事实是,我们大多数人都不知道!指称语义一开始并没有被广泛使用,命令式编程语言的指称语义真的很复杂(在至少对我来说——如果你觉得这很容易,我很乐意让你向我解释)。以任何包含超过 20 行重要代码的命令式程序为例,告诉我它的数学含义是什么。我挑战你。

相比之下,Haskell 的指称语义非常简单。我对 Haskell 知之甚少。除了在 ghci 中乱七八糟之外,我从未在其中进行过任何编码,但它如此强大的原因在于,它的语法比我所知道的任何其他语言都更密切地跟踪语义。作为一种纯粹的、严格的函数式语言,语义就在语法的表面。语法由定义含义的数学概念定义。

事实上,语法和语义是如此密切相关,以至于函数式程序员已经开始将两者混为一谈。 (我谦虚地提交这个意见并等待反弹。)这就是为什么你从谈论价值而不是意义的 FPers 那里得到参照透明度的定义。在像 Haskell 这样的语言中,两者几乎无法区分。由于不存在可变状态,并且每个函数都是纯函数,因此您所要做的就是查看计算函数时产生的值,并且您基本上确定了它的含义。

也可能是新时代 FPer 对引用透明度的解释在某种程度上比我上面总结的更有用。这是不容忽视的。毕竟,如果我上面写的是正确的,那么所有具有指称语义的东西都是参照透明的。不存在非引用透明函数这样的东西,因为每个函数都有数学含义(尽管它可能晦涩难懂且难以定义),并且您始终可以将其替换为具有相同含义的另一个术语。那有什么好处?

嗯,这很好,原因之一。它让我们知道我们不了解杰克我们所做工作背后的数学。就像我上面说的,我不知道Date.now 的指称语义是什么,或者它在数学意义上的含义。它是参照透明的吗?是的,我确信它是,因为它可以被另一个具有相同语义的函数替换。 但我不知道如何评估该函数的语义,因此它的引用透明性对我作为程序员没有用。

因此,如果我从所有这些中学到了一件事,那就是少关注某件事是否符合“引用透明度”的某些定义,而多关注尝试从小程序中制作程序,数学上可组合的部分,具有精确的语义,甚至我都能理解。

【讨论】:

  • 感谢@Roman Sausarnes 的回答,我在#Edit4 中回复了您,谢谢!
  • [将语法映射到含义] C.S. Peirce 引入了“索引”一词来暗示指向的概念(如“食指”)。 ⟦I⟧ ,[[here]],[[now]] 等......大概这是 Haskell 中“Monad”、“functor”事物的数学上相同的概念。即使在 Haskell 中,在指称语义中,[[now]] 作为“食指”也是很清楚的。
【解决方案2】:

Date.now 引用透明吗?

Here's 引用透明的随机数生成器:

...所以如果Date.now 以类似的方式定义,例如:

int Date.now()
{
    return 314159265358979;  // think of it as
                             // a prototype...
}

那么它也将是引用透明的(但不是很有用:-)。

【讨论】: