2015-10-13 5 views
2

Как мы должны писать файлы на Python, оставаясь функционально чистыми? Обычно я хотел бы сделать что-то вроде этого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) 

Мы можем заменить for -loop с чем-то вроде этого

list(map(lambda pack_stream: print_pack(*pack_stream), zip(packs_to_print, output_streams)) 

но только сделать это будет выглядеть, как печать делается функционально. Проблема в том, что print_pack не является чистой функцией, то есть все ее усилия приводят к побочному эффекту и ничего не возвращает. Как мы должны писать файлы и оставаться функционально чистыми (или почти чистыми)?

+3

Зачем вам это нужно? Цикл 'for' является наиболее удобочитаемым, и все это делает« правильный »способ сделать это. –

+0

@tobias_k Я не думаю, что буду использовать его в любом серьезном проекте, но я хочу знать, как это делается. Всегда хорошо знать совершенно другую парадигму, даже если вы не планируете ее использовать. –

+0

Я бы определил метод 'forEach', который бы содержал цикл' for' и использовал его. 'def forEach (iterable, func): для i в iterable: func (i)' then' forEach (input_lines, lambda x: print (x, file = output)) '. (в любом случае, функциональное программирование просто перемещает цикл for в другом месте) – njzk2

ответ

2

По существу, в Python вам нужно иметь нечистую функцию где-то, поэтому нет способа получить 100% -ные функции в этом приложении. В конце вам нужно сделать несколько ввода-вывода, а IO нечисто.

Однако, вы можете попытаться представить определенный уровень абстракции в вашем приложении как чистые функции и изолировать часть, которая делает фактические побочные эффекты в другом модуле. Вы можете сделать это довольно легко по-разному - например, путем накопления содержимого файла, который вы хотите записать, в качестве чистой неизменяемой структуры данных в вашем основном коде. Тогда ваш побочный код может быть уменьшен по размеру, так как все, что ему нужно сделать, это выгрузить строку в файл.

Мы можем посмотреть на Haskell для более строгого способа представления полной мощности побочных операций с чистыми функциями и структурами данных - с использованием абстракции Monad. По сути, Monad - это то, к чему вы можете привязать обратные вызовы, чтобы создать цепочку эффективных вычислений на основе чистых функций. Для монады IO время выполнения Haskell заботится о фактическом выполнении побочных эффектов, как только вы возвращаете значение IO из функции main, поэтому весь написанный вами код является технически чистыми функциями, а среда выполнения заботится о IO.

Библиотека Effect (отказ от ответственности: я ее написал) в основном реализует определенный аромат Монады (или что-то очень близкое к монаде) в Python. Это позволяет вам представлять произвольные 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 декоратора, который позволяет вы используете императивный вид синтаксиса для описания чистой эффектной операции. Выше функция эквивалентна следующему:

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))) 

Они как предполагается, что три функции существуют: open_file, write_data и close_file. Предполагается, что эти функции возвращают объекты Effect, которые представляют намерение выполнить эти действия. В конце концов, эффект по существу является намерением (некоторое прозрачное описание запрошенного действия) и одним или несколькими обратными вызовами для запуска, когда результат этого действия завершен. Интересное различие заключается в том, что write_lines_to_file не фактически записывает строки в файл; он просто возвращает некоторое представление цели , чтобы написать некоторые строки в файл.

Для осуществления этого эффекта вам необходимо использовать функцию sync_perform, например sync_perform(dispatcher, write_lines_to_file(lines, filename)). Это нечистая функция, которая фактически запускает исполнителей для всех эффектов, которые использует ваше чистое представление о эффективном вычислении.

Я мог бы подробно рассказать о том, как должны быть реализованы open_file, write_data и close_file, а также подробные сведения о том, что такое аргумент «диспетчер», но действительно документация на https://effect.readthedocs.org/, вероятно, правильная ссылка на эта точка.

Я также прочитал лекцию на Strange Loop об эффекте и его реализации, которые вы можете посмотреть на YouTube: https://www.youtube.com/watch?v=D37dc9EoFus

Стоит отметить, что эффект является довольно неуклюжим способом сохранить код чисто функциональным. Вы можете получить длинный путь к поддерживаемому коду, используя подход «функциональный ядро ​​/ императив» и старайтесь изо всех сил писать большую часть своего кода как чистые функции и минимизировать эффективный код. Но если вы заинтересованы в более строгом подходе, я думаю, что эффект хорош. Моя команда использует его в производстве, и это очень помогло, особенно с его API тестирования.

Смежные вопросы