代码之家  ›  专栏  ›  技术社区  ›  Tim Matthews

如何编写一个状态不变的应用程序(使用函数式语言)?

  •  6
  • Tim Matthews  · 技术社区  · 16 年前

    有一天我想学函数式编程,但我不明白除了简单的数学,我怎么能用它来做别的事情。

    例如:一个简单的web浏览器添加书签功能需要引起某种变异,这样下次用户单击书签时,新书签就会出现在列表中。

    6 回复  |  直到 16 年前
        1
  •  4
  •   Brian R. Bondy    16 年前

    作为一个整体,一个有用的应用程序通常需要改变一些东西的状态,但这并不意味着所有或您的函数都需要改变状态才能有用。

    Monads 在函数式程序设计中,输入/输出(I/O)操作和状态变化的表示不需要使用引入副作用的语言特性。

    我认为你的想法只是面向对象的。仅仅因为一个函数在给定相同的输入时总是给出相同的输出,并不意味着它不能接受不同类型的输入(可能是无限可能的输入)并产生不同类型的输出。

    在函数式编程中,处理的是不可变的对象,而不是可变的对象。如果您得到一个对象的输入,您可以创建一个新的修改过的对象并返回它。

    看看这个 article about MapReduce by Joel on software . 它包含了一个很好的例子,说明了为什么不改变状态的函数可以完全有用。

        2
  •  4
  •   Brian    16 年前

    你的例子不需要可变状态。对于“添加书签”,可以创建一个新的书签列表,该列表的内容与旧列表相同,但有一个新条目。这是大多数不可变编程的工作方式;您只需创建一个反映世界新状态的新对象图,而不是对现有对象图进行更改。当一切都是不可变的时,很容易在“旧”和“新”版本之间共享对象图的大部分。

    在您的示例中,假设一个“browser”是一个包含以下信息的数据结构对象:当前网页、用户主页和书签列表。这可以在内存中表示为类似于以下内容的树:

    browser = {
     curPage --> "http://current"
     homePage --> "http://home"
     favorites --> { data --> "http://firstFav"
                     next --> { data --> "http://secondFav"
                                next --> null } }
    }
    

    现在“addFavorite”函数将接受这样的数据结构作为输入,并返回一个新的数据结构

    browser = *{
     curPage --> "http://current"
     homePage --> "http://home"
     favorites --> *{ data --> *"http://newFav"*
                     next --> { data --> "http://firstFav"
                                next --> { data --> "http://secondFav"
                                           next --> null } } }*
    }*
    

    标记为“*”的位是新对象-收藏夹前面有一个新的链接列表节点,包含一个新字符串,并且浏览器结构本身是新的(必须是因为它有一个新的“收藏夹”指针)。

    您可以像这样对所有“有状态”计算建模,将“前一个世界”作为输入并返回“下一个世界”作为输出的函数;这是像haskell这样的语言中状态单子的本质。

        3
  •  3
  •   Serafina Brocious    16 年前

    即使是像haskell这样的纯函数语言,你 必须操纵状态。这是通过单子完成的。

        4
  •  2
  •   Paweł Hajdan    16 年前

    处理“易变性”的好例子。假设您用函数式语言实现了一些数据结构,例如avl树。在您实现的函数(插入、删除等)以及内部函数(旋转等)中,实际上并不改变数据,但是 返回 变异数据。

    底层运行时系统确保您的程序将有效地使用内存(例如,它在写时复制和垃圾回收)。

    在程序的一部分 真正地 改变世界状态(i/o、gui)有两种方法。

    • 像haskell这样的纯函数式语言将这些操作封装在monad中(警告:当你读到它们时,你的大脑可能会爆炸,但不要太担心)
    • 其他函数式语言,如ocaml,允许程序中存在可变和副作用。
        5
  •  2
  •   Norman Ramsey    16 年前

    很多其他的答案会让你匆忙使用单子或者其他一些奇异的技术来使用可变状态编程。尽管我亲耳听到haskell 98报告的编辑称haskell为“世界上最好的命令式语言”,但你并不需要像其他答案所建议的那样多的可变状态。在功能程序中, 在函数参数中保持状态 .

    例如,我刚刚写完一个haskell程序,它决定如何将我的音乐备份到dvd上,以便同一张专辑中的歌曲放在同一张dvd上,并且每个dvd(最后一张除外)至少已满99.9%。DVD列表和DVD所在专辑的列表不断变化,但不涉及参考、单子或其他奇异功能。这些值只是递归函数的参数。

    要查看更多示例和解释,请阅读john hughes的非常好的教程文件 Why Functional Programming Matters .

        6
  •  0
  •   Crashworks    16 年前

    而且,在实践中,许多用函数式编程语言编写的应用程序实际上确实使用了一些副作用函数,比如(set!)改变状态。从理论上讲,这并不纯粹,但它确实能完成任务。

    (我特别想到一款流行的消费类软件,它是用lisp派生语言编写的,但必须在恒定内存中运行,所以像垃圾收集这样的东西必须从窗口中消失。)