Как избежать ненужных вычислений при составлении чистых функций на функциональном языке?

У меня есть две функции, которые представляют собой композицию чистых функций. Первая функция берет участок, строит на нем дом и делает снимок для рекламы в журнале:

let buildAndAdvertiseHouse parcel = 
    parcel
    |> inspect
    |> buildWalls
    |> buildRoof
    |> takePhoto
    |> advertise

Вторая функция также берет участок, строит на нем дом и добавляет к нему последний штрих:

let buildAndCompleteHouse parcel = 
    parcel
    |> inspect
    |> buildWalls
    |> buildRoof
    |> paintWalls
    |> addFurniture

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

Как мне реорганизовать мой код, чтобы избежать этих ненужных вычислений, сохранив при этом эти красивые чистые функции, которые имеют ясное значение?


person Tuur    schedule 29.08.2013    source источник
comment
Если parcel не является универсальным типом, вы можете сделать код более идиоматичным с помощью let buildAnd... = inspect >> buildWalls >> ... >> advertise (и с правильным отступом)   -  person Ramon Snir    schedule 29.08.2013
comment
Что касается вопроса, почему бы не разделить ваши две функции на три функции с именами build, advertise и completeHouse?   -  person Ramon Snir    schedule 29.08.2013
comment
Они образуют одну сущность, которую можно использовать в разных частях программы. Конечно, их можно разделить, но тогда эта сущность теряется. В этом случае, если вы хотите их использовать, вам нужно знать внутреннюю работу, а именно то, что она сначала создается, а затем завершается или рекламируется. Я хочу избежать того, что вы должны знать внутреннюю работу.   -  person Tuur    schedule 29.08.2013
comment
Просто вычеркните build   -  person Ingo    schedule 29.08.2013
comment
Если вы не хотите разбивать функции на отдельные части, что видно из ваших комментариев, то вам, вероятно, следует использовать некоторую технику запоминания функций, которые требуют больших вычислительных ресурсов.   -  person Ankur    schedule 29.08.2013


Ответы (1)


Как уже упоминалось в комментариях, я думаю, что лучший подход — превратить общую часть в функцию build. Даже если вы не собираетесь использовать функцию для других целей, это чистый способ структурирования функционального кода.

В F# можно определить тип, представляющий частично построенный дом, но не раскрывающий его внутренности. Это означает, что вызывающие объекты вашей библиотеки могут использовать build для постройки частично построенного дома, но тогда единственное, что они могут с ним сделать, это использовать две предоставленные вами функции:

module Houses = 
  type House = private HouseData of <whatever>
  let build parcel = (...)

  let buildAndAdvertiseHouse house = 
    house
    |> takePhoto
    |> advertise

  let buildAndCompleteHouse house = 
    house
    |> paintWalls
    |> addFurniture

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

Другой подход состоял бы в том, чтобы просто обернуть функциональность в простой тип. F# смешал функциональный и объектно-ориентированный стиль, поэтому нет ничего плохого в том, чтобы иметь тип, который запускает общую часть один раз и сохраняет некоторое состояние.

type House(parcel) = 
  let house = 
    parcel
    |> inspect
    |> buildWalls
    |> buildRoof

  member x.BuildAndAdvertiseHouse()
    house
    |> takePhoto
    |> advertise

  member x.BuildAndCompleteHouse() = 
    house
    |> paintWalls
    |> addFurniture

Это нормально в F#, но я думаю, что предпочел бы функциональный подход с функцией build.

person Tomas Petricek    schedule 29.08.2013