Python:以函数式编程风格编写文件

Python:以函数式编程风格编写文件,python,python-3.x,io,functional-programming,Python,Python 3.x,Io,Functional Programming,我们应该如何在保持功能纯粹的同时用Python编写文件?通常我会这样做 from typing import Iterable from io import IOBase def transform_input(input_lines: Iterable[str]) -> Iterable[str]: ... def print_pack(input_lines: Iterable[str], output: IOBase) -> None: for line in

我们应该如何在保持功能纯粹的同时用Python编写文件?通常我会这样做

from typing import Iterable
from io import IOBase


def transform_input(input_lines: Iterable[str]) -> Iterable[str]: ...


def print_pack(input_lines: Iterable[str], output: IOBase) -> None:
    for line in input_lines:
        print(line, file=output)


def main(*args, **kwargs):
    # Somehow we get a bunch iterables with strings and a list of output streams
    packs_of_input = ... # Iterable[Iterable[str]]
    output_streams = ... # Iterable[IOBase]
    packs_to_print = map(transform_input, packs_of_input)
    for pack, output_stream in zip(packs_to_print, output_streams):
        print_pack(pack, output_stream)
我们可以将-循环的
替换为类似的内容

list(map(lambda pack_stream: print_pack(*pack_stream), zip(packs_to_print, output_streams))
但这只会让打印看起来像是功能性的。问题是,
print\u-pack
不是一个纯粹的函数,它的所有努力都会产生副作用,并且不会返回任何结果。
我们应该如何编写文件并保持功能上的纯净(或几乎纯净)

本质上,在Python中,您需要在某个地方有一个不纯函数,因此在这个应用程序中不可能有100%纯函数。最后你需要做一些IO,而IO是不纯洁的

但是,您可以尝试将应用程序中的特定抽象层表示为纯函数,并在另一个模块中隔离产生实际副作用的部分。您可以通过一种特别的方式很容易地做到这一点——例如,通过在主代码中将要编写的文件的内容累积为一个纯不可变的数据结构。这样,您的副作用代码的大小就可以减小,因为它所需要做的就是将字符串转储到文件中

我们可以向Haskell寻求一种更严格的方法,用纯函数和数据结构来纯粹地表示副作用操作的全部功能——使用Monad抽象。从本质上讲,Monad是可以绑定回调的东西,以创建一系列基于纯函数的有效计算。对于IO monad,Haskell运行时负责在您从
main
函数返回IO值后实际执行副作用——因此您编写的所有代码在技术上都是纯函数,而运行时负责IO

该库(免责声明:我编写的)基本上在Python中实现了某种风格的Monad(或非常接近Monad的东西)。这允许您将任意IO(以及其他副作用)表示为纯对象和函数,并将这些效果的实际性能放在一边。所以你的应用程序代码可以是100%纯的,只要你有一个相对简单的副作用函数库

例如,要实现一个函数,将行列表写入具有效果的文件,您可以执行以下操作:

@do
def write_lines_to_file(lines, filename):
    file_handle = yield open_file(filename)
    for line in lines:
        yield write_data(file_handle, line)
    # alternatively:
    # from effect.fold import sequence; from functools import partial
    # yield sequence(map(partial(write_data, file_handle), lines))
    yield close_file(file_handle)
效果库提供了这个特殊的
do
decorator,允许您使用命令式语法来描述纯有效的操作。上述功能等同于此功能:

def write_lines_to_file(lines, filename):
    file_handle_eff = open_file(filename).on(
        lambda file_handle:
            sequence(map(partial(write_data, file_handle), lines)).on(
                lambda _: close_file(file_handle)))
它们都假设存在三个函数:打开\ U文件、写入\ U数据和关闭\ U文件。假设这些函数返回表示执行这些操作意图的效果对象。最后,效果本质上是一种意图(对请求的操作的透明描述),以及在该操作的结果完成时运行的一个或多个回调。有趣的区别在于,向文件中写入行实际上并不向文件中写入行;它只是返回一些表示将某些行写入文件的意图的表示

要实际执行此效果,您需要使用
sync\u-perform
功能,如
sync\u-perform(dispatcher,write\u-lines\u-To\u-file(lines,filename))
。这是一个不纯函数,它实际运行执行者以获得有效计算的纯表示所使用的所有效果

我可以详细介绍如何实现open_文件、write_数据和close_文件,以及“dispatcher”参数的细节,但实际上,此时引用的文档可能是正确的

我还在Strange Loop上做了一个关于效果及其实现的演讲,你们可以在YouTube上看到:


值得注意的是,Effect是一种让代码保持纯功能性的非常严厉的方法。通过采用“功能性核心/命令式外壳”方法,尽最大努力将大部分代码编写为纯函数,并尽量减少有效代码,您可以在实现可维护代码方面取得长足的进步。但如果你对更严格的方法感兴趣,我认为效果是好的。我的团队在生产中使用了它,它帮助了很多,特别是在测试API方面。

为什么要这样做?
for
循环是最可读的,而且是最“正确”的方法。@tobias_k我想我不会在任何严肃的项目中使用它,但我想知道这些事情是如何完成的。知道一个完全不同的范例总是一件好事,即使你不打算使用它。我将定义一个
forEach
方法,该方法将包含
for
循环,并使用它
def forEach(iterable,func):对于iterable中的i:func(i)
然后
forEach(输入行,lambda x:print(x,file=output))
。(无论如何,函数编程只是将for循环移到其他地方)这是什么@从马是的,看起来很有希望。