Архитектура приложения / композиция в F #

В последнее время я делал SOLID в C # на довольно экстремальном уровне и в какой-то момент осознал, что в настоящее время я по сути делаю не что иное, как создание функций в настоящее время. И после того, как я недавно снова начал смотреть на F #, я подумал, что это, вероятно, будет гораздо более подходящим выбором языка для большей части того, что я делаю сейчас, поэтому я хотел бы попробовать перенести реальный проект C # на F # как доказательство концепции. Я думаю, что мог бы вытащить реальный код (очень идиоматическим образом), но я не могу представить, как будет выглядеть архитектура, позволяющая мне работать так же гибко, как в C #.

Под этим я подразумеваю, что у меня есть множество небольших классов и интерфейсов, которые я создаю с использованием контейнера IoC, и я также часто использую такие шаблоны, как Decorator и Composite. Это приводит к (на мой взгляд) очень гибкой и расширяемой общей архитектуре, которая позволяет мне легко заменять или расширять функциональность в любой точке приложения. В зависимости от того, насколько велико требуемое изменение, мне может понадобиться только написать новую реализацию интерфейса, заменить ее при регистрации IoC и сделать это. Даже если изменение будет больше, я могу заменить части графа объектов, в то время как остальная часть приложения просто останется прежней.

Теперь с F # у меня нет классов и интерфейсов (я знаю, что могу, но я думаю, что это не относится к моменту, когда я хочу заниматься фактическим функциональным программированием), у меня нет внедрения в конструктор, и у меня нет IoC контейнеры. Я знаю, что могу сделать что-то вроде шаблона Decorator, используя функции более высокого порядка, но это, похоже, не дает мне такой же гибкости и удобства обслуживания, как у классов с инжекцией конструктора.

Рассмотрим эти типы C #:

public class Dings
{
    public string Lol { get; set; }

    public string Rofl { get; set; }
}

public interface IGetStuff
{
    IEnumerable<Dings> For(Guid id);
}

public class AsdFilteringGetStuff : IGetStuff
{
    private readonly IGetStuff _innerGetStuff;

    public AsdFilteringGetStuff(IGetStuff innerGetStuff)
    {
        this._innerGetStuff = innerGetStuff;
    }

    public IEnumerable<Dings> For(Guid id)
    {
        return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
    }
}

public class GeneratingGetStuff : IGetStuff
{
    public IEnumerable<Dings> For(Guid id)
    {
        IEnumerable<Dings> dingse;

        // somehow knows how to create correct dingse for the ID

        return dingse;
    }
}

Я скажу мой контейнер IoC, чтобы решитьAsdFilteringGetStuff заIGetStuff а такжеGeneratingGetStuff для его собственной зависимости с этим интерфейсом. Теперь, если мне нужен другой фильтр или вообще удалить фильтр, мне может понадобиться соответствующая реализацияIGetStuff а затем просто измените регистрацию IoC. Пока интерфейс остается прежним, мне не нужно ничего трогатьв приложение. OCP и LSP, включенные DIP.

Что мне теперь делать в F #?

type Dings (lol, rofl) =
    member x.Lol = lol
    member x.Rofl = rofl

let GenerateDingse id =
    // create list

let AsdFilteredDingse id =
    GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")

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

Следующая попытка:

let GenerateDingse id =
    // create list

let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
    generator id |> List.filter (fun x -> x.Lol = "asd")

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

Что еще я мог сделать? Я мог бы подражать понятию «составной корень» из моего C # SOLID в последнем файле проекта F #. Большинство файлов - это просто набор функций, тогда у меня есть своего рода «реестр», который заменяет контейнер IoC, и, наконец, есть одна функция, которую я вызываю для фактического запуска приложения и которая использует функции из «реестра». В «реестре» я знаю, что мне нужна функция типа (Guid -> Dings list), которую я назовуGetDingseForId, Это то, что я называю, а не отдельные функции, определенные ранее.

Для декоратора определение будет

let GetDingseForId id = AsdFilteredDingse GenerateDingse

Чтобы удалить фильтр, я бы изменил это на

let GetDingseForId id = GenerateDingse

Недостатком (?) Этого является то, что все функции, использующие другие функции, должны быть функциями более высокого порядка, а мой «реестр» должен отображатьсявсе функции, которые я использую, потому что фактические функции, определенные ранее, не могут вызывать какие-либо функции, определенные позже, в частности, не из «реестра». Я мог бы также столкнуться с круговыми проблемами зависимости с отображениями «реестра».

Имеет ли это смысл? Как вы действительно строите приложение F #, чтобы его можно было поддерживать и развивать (не говоря уже о тестировании)?

Ответы на вопрос(1)

Ваш ответ на вопрос