Functional programming 理解引用透明度

Functional programming 理解引用透明度,functional-programming,referential-transparency,Functional Programming,Referential Transparency,一般来说,我头痛是因为我的推理有问题: 对于1组参数,引用透明函数将始终返回1组输出值 这意味着这样的函数可以表示为真值表(一个表,其中为一组参数指定了一组输出参数) 这使得这些函数背后的逻辑是组合的(与顺序相反) 这意味着使用纯函数式语言(只有rt函数)可以只描述组合逻辑 最后一句话是根据这个推理得出的,但显然是错误的;这意味着推理有错误[问题:这种推理的错误在哪里?] UPD2。你们,伙计们,说了很多有趣的东西,但并没有回答我的问题。我现在更明确地定义了它。抱歉弄乱了问题的定义 据我所知,引

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

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

  • 这意味着这样的函数可以表示为真值表(一个表,其中为一组参数指定了一组输出参数)

  • 这使得这些函数背后的逻辑是组合的(与顺序相反)


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

  • 最后一句话是根据这个推理得出的,但显然是错误的;这意味着推理有错误[问题:这种推理的错误在哪里?]


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

    据我所知,引用透明只是意味着:当使用相同的参数调用某个给定函数时,它总是会产生相同的结果。所以,你在学校学到的数学函数是透明的

    您可以使用一种语言来学习如何使用纯函数式语言进行操作。有一些方法可以使用“可更新的存储可能性”,例如读卡器Monad和。如果您对纯函数数据结构感兴趣,可能是一本不错的读物

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


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

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

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

    首先,假设您使用命令式语言(如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!
    
    当然,但它不是很有用<代码>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;
        }
    }
    
    现在,对于函数,我们不改变状态,但是变量可以改变。一旦一个变量进入我们的函数,它就会被锁定。但是,我们可以再次调用该函数(递归),这就像得到一组全新的变量(旧变量保持不变)。尽管有多个
    计数
    总和((int[]){1,2,3},3)
    的计算结果总是
    6
    ,因此如果愿意,可以用
    6
    替换该表达式

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


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

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

    my_sum numbers = ??? where
        i     = 0
        total = 0
    
    在这里,您不能编写一个“for循环”来随时间递增i和total。不过,一切都没有失去。只需使用递归不断获取新的
    i
    s和
    total
    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)
    
    其思想是,main不是一个由语句列表组成的函数,而是一个Haskell执行的IO动作,动作通过bind操作定义并链接在一起。此外,可以使用
    return
    函数定义不执行任何操作(生成任意值)的操作

    请注意,绑定和返回并不特定于操作。它们可以与任何称自己为单子的类型一起使用,以完成各种令人生厌的事情

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

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

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

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

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

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

    您可以从函数式语言的引用透明特性得出结论。到目前为止,这个结论听起来似乎是合理的,但您可以监督函数是否能够接受集合作为输入,并与逻辑门的固定输入相反地处理它们

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

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

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