理解引用透明度

发布于 2024-09-09 17:10:35 字数 392 浏览 2 评论 0原文

一般来说,我很头痛,因为我的推理有问题:

  1. 对于 1 组参数,引用透明函数将始终返回 1 组输出值。

  2. 这意味着这样的函数可以表示为真值表(为 1 组参数指定 1 组输出参数的表)。

  3. 这使得这些函数背后的逻辑组合(而不是顺序)

  4. 这意味着使用纯函数语言(仅具有 rt 函数)可以仅描述组合逻辑。

最后一个陈述是从这个推理得出的,但是它显然是错误的;这意味着推理存在错误。 [问题:这个推理错误在哪里?]

UPD2。你们,伙计们,说了很多有趣的话,但没有回答我的问题。我现在更明确地定义了它。抱歉搞乱了问题定义!

Generally, I have a headache because something is wrong with my reasoning:

  1. For 1 set of arguments, referential transparent function will always return 1 set of output values.

  2. that means that such function could be represented as a truth table (a table where 1 set of output parameters is specified for 1 set of arguments).

  3. that makes the logic behind such functions is combinational (as opposed to sequential)

  4. that means that with pure functional language (that has only rt functions) it is possible to describe only combinational logic.

The last statement is derived from this reasoning, but it's obviously false; that means there is an error in reasoning. [question: where is error in this reasoning?]

UPD2. You, guys, are saying lots of interesting stuff, but not answering my question. I defined it more explicitly now. Sorry for messing up with question definition!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

酒几许 2024-09-16 17:10:35

问题:这个推理错误在哪里?

引用透明函数可能需要无限真值表来表示其行为。您将很难用组合逻辑设计无限电路。

另一个错误:时序逻辑的行为可以纯粹从功能上表示为从状态到状态的函数。在实现中,这些状态按时间顺序发生的事实并不妨碍定义一个纯粹的引用透明函数,该函数描述状态如何随时间演变。

Question: where is error in this reasoning?

A referentially transparent function might require an infinite truth table to represent its behavior. You will be hard pressed to design an infinite circuit in combinatory logic.

Another error: the behavior of sequential logic can be represented purely functionally as a function from states to states. The fact that in the implementation these states occur sequentially in time does not prevent one from defining a purely referentially transparent function which describes how state evolves over time.

情痴 2024-09-16 17:10:35

编辑:虽然我显然错过了实际问题的中心,但我认为我的答案非常好,所以我保留它:-)(见下文)。

我想用一种更简洁的方式来表达这个问题可能是:纯函数式语言可以计算命令式语言可以计算的任何东西吗?

首先,假设您采用了像 C 这样的命令式语言,并使其在定义变量后无法更改。例如:

int i;

for (i = 0;  // okay, that's one assignment
     i < 10; // just looking, that's all
     i++)    // BUZZZ!  Sorry, can't do that!

嗯,你的 for 循环开始了。我们可以保留 while 循环吗?

while (i < 10)

当然可以,但是不是很有用。 i 无法更改,因此它要么永远运行,要么根本不运行。

递归怎么样?是的,你可以保持递归,它仍然很有用:

int sum(int *items, unsigned int count)
{
    if (count) {
        // count the first item and sum the rest
        return *items + sum(items + 1, count - 1);
    } else {
        // no items
        return 0;
    }
}

现在,对于函数,我们不会改变状态,但变量可以改变。一旦变量传递到我们的函数中,它就会被锁定。但是,我们可以再次调用该函数(递归),这就像获得一组全新的变量(旧变量保持不变)。尽管有多个 itemscount 实例,但 sum((int[]){1,2,3}, 3) 始终会计算结果为 6,因此您可以根据需要将该表达式替换为 6

我们还能做任何我们想做的事吗?我不是100%确定,但我认为答案是“是”。不过,如果你有倒闭的话,你当然可以。


你说得对。这个想法是,一旦定义了变量,就不能重新定义它。给定相同的变量,引用透明表达式总是产生相同的结果值。

我建议研究 Haskell,一种纯函数式语言。严格来说,Haskell 没有“赋值”运算符。例如:

my_sum numbers = ??? where
    i     = 0
    total = 0

在这里,您不能编写一个“for 循环”来增加 i 和总计。不过,一切并没有失去。只需使用递归来不断获取新的 itotal

my_sum numbers = f 0 0 where
    f i total =
        if i < length numbers
            then f i' total'
            else total
        where
            i' = i+1
            total' = total + (numbers !! i)

(请注意,这是在 Haskell 中对列表求和的愚蠢方法,但它演示了一种方法处理单个赋值的问题。)

现在,考虑一下这段看起来高度命令式的代码:

main = do
    a <- readLn
    b <- readLn
    print (a + b)

它实际上是语法糖:

main =
    readLn >>= (\a ->
    readLn >>= (\b ->
    print (a + b)))

其想法是,main 不是一个由语句列表组成的函数,而是一个 IO 操作, Haskell 执行,操作被定义并与绑定操作链接在一起。此外,可以使用 return 函数定义不执行任何操作但产生任意值的操作。

请注意,绑定和返回并不特定于操作。它们可以与任何自称为 Monad 的类型一起使用来完成各种时髦的事情。

为了澄清这一点,请考虑 readLn。 readLn 是一个操作,如果执行该操作,将从标准输入读取一行并生成其解析值。要使用该值执行某些操作,我们不能将其存储在变量中,因为这会违反引用透明度

a = readLn

如果允许这样做,a 的值将取决于世界,并且每次调用时都会不同readLn,这意味着 readLn 不会是引用透明的。

相反,我们将 readLn 操作绑定到处理该操作的函数,从而生成一个新操作,如下所示:

readLn >>= (\x -> print (x + 1))

该表达式的结果是一个操作值。如果 Haskell 从沙发上起来并执行此操作,它将读取一个整数,递增它,然后打印它。通过将动作的结果绑定到对结果执行某些操作的函数,我们可以在状态世界中玩耍时保持引用透明度。

Edit: Although I apparently missed the bullseye on the actual question, I think my answer is pretty good, so I'm keeping it :-) (see below).

I guess a more concise way to phrase the question might be: can a purely functional language compute anything an imperative one can?

First of all, suppose you took an imperative language like C and made it so you can't alter variables after defining them. E.g.:

int i;

for (i = 0;  // okay, that's one assignment
     i < 10; // just looking, that's all
     i++)    // BUZZZ!  Sorry, can't do that!

Well, there goes your for loop. Do we get to keep our while loop?

while (i < 10)

Sure, but it's not very useful. i can't change, so it's either going to run forever or not run at all.

How about recursion? Yes, you get to keep recursion, and it's still plenty useful:

int sum(int *items, unsigned int count)
{
    if (count) {
        // count the first item and sum the rest
        return *items + sum(items + 1, count - 1);
    } else {
        // no items
        return 0;
    }
}

Now, with functions, we don't alter state, but variables can, well, vary. Once a variable passes into our function, it's locked in. However, we can call the function again (recursion), and it's like getting a brand new set of variables (the old ones stay the same). Although there are multiple instances of items and count, sum((int[]){1,2,3}, 3) will always evaluate to 6, so you can replace that expression with 6 if you like.

Can we still do anything we want? I'm not 100% sure, but I think the answer is "yes". You certainly can if you have closures, though.


You have it right. The idea is, once a variable is defined, it can't be redefined. A referentially transparent expression, given the same variables, always yields the same result value.

I recommend looking into Haskell, a purely functional language. Haskell doesn't have an "assignment" operator, strictly speaking. For instance:

my_sum numbers = ??? where
    i     = 0
    total = 0

Here, you can't write a "for loop" that increments i and total as it goes along. All is not lost, though. Just use recursion to keep getting new is and totals:

my_sum numbers = f 0 0 where
    f i total =
        if i < length numbers
            then f i' total'
            else total
        where
            i' = i+1
            total' = total + (numbers !! i)

(Note that this is a stupid way to sum a list in Haskell, but it demonstrates a method of coping with single assignment.)

Now, consider this highly imperative-looking code:

main = do
    a <- readLn
    b <- readLn
    print (a + b)

It's actually syntactic sugar for:

main =
    readLn >>= (\a ->
    readLn >>= (\b ->
    print (a + b)))

The idea is, instead of main being a function consisting of a list of statements, main is an IO action that Haskell executes, and actions are defined and chained together with bind operations. Also, an action that does nothing, yielding an arbitrary value, can be defined with the return function.

Note that bind and return aren't specific to actions. They can be used with any type that calls itself a Monad to do all sorts of funky things.

To clarify, consider readLn. readLn is an action that, if executed, would read a line from standard input and yield its parsed value. To do something with that value, we can't store it in a variable because that would violate referential transparency:

a = readLn

If this were allowed, a's value would depend on the world and would be different every time we called readLn, meaning readLn wouldn't be referentially transparent.

Instead, we bind the readLn action to a function that deals with the action, yielding a new action, like so:

readLn >>= (\x -> print (x + 1))

The result of this expression is an action value. If Haskell got off the couch and performed this action, it would read an integer, increment it, and print it. By binding the result of an action to a function that does something with the result, we get to keep referential transparency while playing around in the world of state.

酒与心事 2024-09-16 17:10:35

据我了解,引用透明度只是意味着:当使用相同的参数调用时,给定的函数将始终产生相同的结果。因此,您在学校学到的数学函数是引用透明的。

为了了解纯函数式语言是如何完成工作的,您可以查看 Haskell 语言。有多种方法可以使用“可更新存储的可能性”,例如 Reader Monad 和 State Monad例子。如果您对纯函数式数据结构感兴趣,Okasaki 可能是一个不错的选择读。

是的,你是对的:在像 Haskell 这样的纯函数式语言中,求值顺序并不像在非函数式语言中那样重要,因为如果没有副作用,就没有理由在其他事情之前/之后做一些事情——除非一个的输入依赖于另一个的输出,或者像单子这样的手段发挥作用。

我真的不知道真值表问题。

As far as I understand it, referential transparency just means: A given function will always yield the same result when invoked with the same arguments. So, the mathematical functions you learned about in school are referentially transparent.

A language you could check out in order to learn how things are done in a purely functional language would be Haskell. There are ways to use "updateable storage possibilities" like the Reader Monad, and the State Monad for example. If you're interested in purely functional data structures, Okasaki might be a good read.

And yes, you're right: Order of evaluation in a purely functional language like haskell does not matter as in non-functional languages, because if there are no side effects, there is no reason to do someting before/after something else -- unless the input of one depends on the output of the other, or means like monads come into play.

I don't really know about the truth-table question.

鹿童谣 2024-09-16 17:10:35

这是我回答这个问题的尝试:

任何系统都可以被描述为一个组合函数,无论大小。

纯函数只能处理组合逻辑的推理没有任何问题——这是真的,只是函数式语言在某种程度上向你隐藏了这一点。

例如,您甚至可以将游戏引擎的工作原理描述为真值表或组合函数。

您可能有一个确定性函数,它将“整个游戏的当前状态”作为游戏引擎和键盘输入占用的 RAM,并返回“一帧后的游戏状态”。返回值将由输入中的位组合确定。

当然,在任何有意义且健全的函数中,输入都会被解析为整数、小数和布尔值块,但这些值中的位组合仍然决定函数的输出。

还要记住,基本的数字逻辑可以用真值表来描述。除了 4 位整数算术之外,不这样做的唯一原因是真值表的大小呈指数增长。

Here's my stab at answering the question:

Any system can be described as a combinatorial function, large or small.

There's nothing wrong with the reasoning that pure functions can only deal with combinatorial logic -- it's true, just that functional languages hide that from you to some extent or another.

You could even describe, say, the workings of a game engine as a truth table or a combinatorial function.

You might have a deterministic function that takes in "the current state of the entire game" as the RAM occupied by the game engine and the keyboard input, and returns "the state of the game one frame later". The return value would be determined by the combinations of the bits in the input.

Of course, in any meaningful and sane function, the input is parsed down to blocks of integers, decimals and booleans, but the combinations of the bits in those values is still determining the output of your function.

Keep in mind also that basic digital logic can be described in truth tables. The only reason that that's not done for anything more than, say, arithmetic on 4-bit integers, is because the size of the truth table grows exponentially.

小嗲 2024-09-16 17:10:35

您的推理错误如下:
“这意味着这样的函数可以表示为真值表”。

您可以根据函数式语言的引用透明度属性得出这一结论。到目前为止,结论听起来似乎合理,但您发现函数能够接受集合作为输入并处理它们,这与逻辑门的固定输入形成鲜明对比。

因此,函数并不等于逻辑门,而是取决于实际(在运行时确定)输入的逻辑门的构造计划!

评论您的评论:函数式语言尽管无状态,但可以通过每次访问时从头开始构造状态来实现状态机。

The error in Your reasoning is the following:
"that means that such function could be represented as a truth table".

You conclude that from a functional language's property of referential transparency. So far the conclusion would sound plausible, but You oversee that a function is able to accept collections as input and process them in contrast to the fixed inputs of a logic gate.

Therefore a function does not equal a logic gate but rather a construction plan of such a logic gate depending on the actual (at runtime determined) input!

To comment on Your comment: Functional languages can - although stateless - implement a state machine by constructing the states from scratch each time they are being accessed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文