【问题标题】:understanding referential transparency理解参照透明度
【发布时间】:2011-03-17 03:56:42
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

  • “这意味着使用纯函数式语言......可以只描述顺序逻辑”-您不是更喜欢“组合逻辑”吗?
  • 非常感谢!这很关键!
  • 您能否解释一下为什么“纯函数式语言只能描述组合逻辑”的说法是错误的?谢谢。
  • @Dave:因为您可以使用纯函数式语言实现状态机。 (我实际上假设 - 但否则函数式语言对任何目的都没有用)
  • 图灵完备机也有无限递归,所以函数可能会调用自己。

标签: functional-programming referential-transparency


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

    首先,假设您采用了一种命令式语言(如 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;
        }
    }
    

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

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


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

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

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

    在这里,您不能编写一个“for 循环”来递增 i 和 total 。不过,一切都没有丢失。只需使用递归来不断获得新的is 和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)
    

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

    现在,考虑一下这个看起来非常命令式的代码:

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

    它实际上是语法糖:

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

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

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

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

    a = readLn
    

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

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

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

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

    【讨论】:

    • 我真的很喜欢“如果 Haskell 从沙发上下来……”这些天该死的懒惰语言。
    • 我读了你的回答大约 10 次,但仍然无法得到 - 状态变化在哪里?什么功能应该有状态?我仍然只看到组合逻辑
    • 你可以举一个命令式语法的例子吗? - 如果你避免变量赋值(函数返回值除外)并保持函数引用透明 - 它不应该做同样的伎俩吗?
    • 我理解正确吗?没有递归,纯函数式程序就没有状态?
    【解决方案3】:

    据我了解,引用透明性只是意味着:当使用相同的参数调用给定的函数时,总是会产生相同的结果。所以,你在学校学过的数学函数是参照透明的。

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

    是的,你是对的:在像 haskell 这样的纯函数式语言中,评估顺序与在非函数式语言中并不重要,因为如果没有副作用,就没有理由在某事之前/之后做某事else -- 除非一个的输入依赖于另一个的输出,或者像 monad 这样的手段发挥作用。

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

    【讨论】:

    • 请注意:“Monad”确实没有什么神秘之处。 Haskell 不允许 C++ 风格的重载;它使用类型类。 “Monad”只是许多类型订阅的类型类,因此它们可以使用漂亮的&gt;&gt;= 和return 函数。当你看到IO Int 时,想想一个动作,如果它被执行,就会产生一个 Int。您使用&gt;&gt;=(绑定)运算符来构建复合动作,而不是编写逐步执行的指令。 &gt;&gt;= 运算符以时髦有趣的方式用于其他类型。不要害怕“M”字。
    • 是的,基本上,例如,您可以仅通过链接函数调用来强制评估顺序,甚至无需触及 monad。
    • @danlei:链接函数调用本身不应该强制执行惰性语言的执行顺序,至少一般情况下不会。函数本身必须以这种方式编写。 IO的bind算子就是这样一个函数,这就是为什么它可以用于排序动作。
    • @Chuck:你说得对,感谢您的澄清。我想说的是,如果我们(分别是顶层)评估某些东西,则必须评估依赖于它的所有内容。如果我们想象一个依赖链,传递某种“世界状态”,我们基本上有类似 IO monad 的东西(好吧,这个版本允许时间旅行并不容易),可以想象为隐式传递世界大约。我最后的评论确实太笼统了,也表达得不好。希望这次我做对了。 :)
    • s/取决于它/它取决于/
    【解决方案4】:

    这是我回答问题的尝试:

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

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

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

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

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

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

    【讨论】:

    • 在核心中,您的回答与我试图解释的内容相同。
    【解决方案5】:

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

    您从函数式语言的引用透明性属性中得出结论。到目前为止,这个结论听起来似乎是合理的,但是您注意到一个函数能够接受集合作为输入并处理它们,而不是逻辑门的固定输入。

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

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

    【讨论】:

    • 图灵完备机也有无限递归,所以函数可能会调用自己。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-18
    • 1970-01-01
    • 2011-03-11
    • 2021-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多