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

Это вопрос о том, как я должен организовать свой код F#. Надеюсь, это не нарушение SO правил.

У меня есть десятки исходных файлов (имена, оканчивающиеся на .fs) в моем проекте. Каждый файл содержит модуль. В некоторых из этих файлов/модулей я определяю только функции. В других я определяю функции и другие значения (не функции).

Последний файл в Solution Explorer (Visual Studio) — это Program.fs, который на самом деле содержит очень мало кода. Большинство расчетов было сделано «над» ним.

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

Преимущества:

1) Лучшее представление о потоке программы.

2) Проще выделить весь код выше определенной строки и отправить на исполнение в FSI.

3) Немного проще искать эти значения в редакторе.

4) Возможно, проще отладить, поставив точки останова на строки, где объявлены значения.

Недостатки:

1) Program.fs мог стать большим и громоздким.

2) Потеря модульности.

3) После внесения изменений, если вычисление значения y в модуле B зависит от значения x в модуле A "над" ним, то я больше не могу иметь y в качестве значения, оно должно быть объявлено как функция x. Точно так же, если объявление функции в модуле B зависит от значения в модуле A, я должен добавить параметр в определение функции.

Ниже приведены два примера одной и той же небольшой программы, созданной двумя альтернативными методами. Какой из двух лучше вообще?

// ///////////////// Values in modules \\\\\\\\\\\\\\\\\\\\

// File A.fs

module A

let i = 1
let add x y : float = x + y

// File B.fs

module B

let sqr z = z * z + float i
let x = sqr 99.9

// File Program.fs

open A
open B

let y =
    add (float i) x 
    |> sqr

printfn "%f" y

[<EntryPoint>]
let main argv = 
    printfn "%A" argv
    0 // return an integer exit code

// This is the calculated value for y: 99640524.640100

// ///////////////// Values in Program.fs \\\\\\\\\\\\\\\\\\\\

// File A.fs

module A

let add x y : float = x + y

// File B.fs

module B

open A

let sqr i z = z * z + float i // notice the additional parameter

//File Program.fs

open A
open B

let i = 1
let x = sqr i 99.9

let y =
    add (float i) x 
    |> sqr i

printfn "%f" y

[<EntryPoint>]
let main argv = 
    printfn "%A" argv
    0 // return an integer exit code

// This is the calculated value for y: 99640524.640100

person Soldalma    schedule 14.12.2017    source источник
comment
Просто чтобы лучше понять, насколько велики и сколько у вас файлов?   -  person Tomas Petricek    schedule 15.12.2017
comment
В моем проекте 60 файлов. Самый большой (на сегодняшний день) файл содержит 2600 строк (примерно 12 классов, это единственный файл с классами). Тогда у меня есть несколько строк по 200-500, а остальные маленькие. Будет добавлено много других классов. Файлов, содержащих нефункциональные значения, всего около 5.   -  person Soldalma    schedule 15.12.2017


Ответы (1)


Как вы представили, вторая версия (со значениями, перемещенными в Main) имхо лучше. Вы в значительной степени добились этого с преимуществом №1, и оно действительно большое. Что касается перечисленных Вами недостатков:

  1. Большой основной: Да, зависит от того, сколько всего мы говорим, в худшем случае вы можете сохранить значения в еще одном модуле, используемом только основным и только для значений. Подумайте о "Модуле конфигурации"
  2. Потеря модульности: я не понимаю, почему. Во всяком случае, это увеличивает модульность? Ваш main не зависит от того, имеет ли модуль X какое-то значение, он его предоставляет. Затем вы можете заменить модуль другим, удовлетворяющим тому же интерфейсу, и не заботиться о волновом эффекте, который он может оказать на другие модули. Если у вас есть большая иерархия модулей, вы можете представить ее в своем основном в соответствии с принципом инверсии зависимостей - это потребует некоторой работы, но хорошая новость заключается в том, что в функциональных языках вам не нужны контейнеры IoC, частичное приложение делает работу
  3. Если модуль B зависит от значения, существующего в модуле A, он не очень модульный с самого начала, не так ли? Хорошо, что вам придется изменить его на функцию — она будет явно говорить то, что сейчас неявно

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

person DevNewb    schedule 15.12.2017