了解参考透明度

时间:2010-07-16 20:22:58

标签: functional-programming referential-transparency

一般来说,我很头疼因为我的推理出了问题:

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

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

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

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

  5. 最后一个语句来源于这个推理,但是它显然是假的;这意味着推理有误。 [问题:这个推理在哪里出错?]

    UPD2。你们这些人说的很多有趣的东西,但没有回答我的问题。我现在更明确地定义它。对不起弄乱问题定义!

5 个答案:

答案 0 :(得分:5)

  

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

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

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

答案 1 :(得分:3)

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

我想用一种更简洁的方式来表达这个问题可能是:一个纯粹的功能性语言可以计算出任何必要的东西吗?

首先,假设您使用了像C这样的命令式语言,并且在定义它们之后无法更改变量。 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!

嗯,你的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。< / p>

我们还能做任何我们想做的事吗?我不是百分百肯定,但我认为答案是肯定的。但是,如果你有封闭,你当然可以。


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

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

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

在这里,你不能写一个“for循环”,它增加i和总和。不过,一切都不会丢失。只需使用递归来获取新的itotal s:

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不是由一个语句列表组成的函数,而是一个Haskell执行的IO动作,并且动作是通过绑定操作定义和链接在一起的。此外,可以使用return函数定义不执行任何操作,产生任意值的操作。

请注意,绑定和返回并非特定于操作。它们可以用于任何称自己为Monad的类型来做各种各样的时髦事物。

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

a = readLn

如果允许这样做,那么a的值将取决于世界,并且每次调用readLn时都会有所不同,这意味着readLn不会被公开透明。

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

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

此表达式的结果是一个动作值。如果Haskell离开沙发并执行此操作,它将读取整数,递增并打印它。通过将动作的结果绑定到对结果执行某些操作的函数,我们可以在状态世界中保持参照透明度。

答案 2 :(得分:2)

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

您可以查看的语言是Haskell,以便了解如何使用纯函数语言完成工作。有一些方法可以使用“可更新的存储可能性”,例如Reader Monad和State Monad。如果您对纯功能数据结构感兴趣,Okasaki可能是一个很好的阅读。

是的,你是对的:像非功能语言这样的纯功能语言中的评估顺序无关紧要,因为如果没有副作用,就没有理由在某事之前/之后做某事否则 - 除非一个的输入取决于另一个的输出,或者像monad这样的方式起作用。

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

答案 3 :(得分:1)

这是我回答这个问题的准备:

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

纯函数只能处理组合逻辑的原因并没有错 - 这是正确的,只是函数式语言在某种程度上隐藏了你。

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

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

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

请记住,基本数字逻辑可以在真值表中描述。唯一的原因是,除了4位整数的算术之外,还没有做到这一点,因为真值表的大小呈指数级增长。

答案 4 :(得分:0)

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

您可以从功能语言的参照透明度属性中得出结论。到目前为止,结论听起来似乎有道理,但你监督一个函数能够接受集合作为输入并与逻辑门的固定输入相比较处理它们。

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

评论您的评论:功能语言可以 - 尽管是无状态的 - 通过每次访问时从头开始构建状态来实现状态机。