Как правильно разбить код на разделы в функциональной библиотеке C#?

в качестве предпосылки одним из ключевых различий дизайна FP о многоразовых библиотеках (для того, что я изучаю) является то, что они более ориентированы на данные, соответствующие OO (в целом).

это, кажется, подтверждается также из новых методов, таких как TFD (типа-Первый-развитие), хорошо объяснил исполнителя Tomas Petricek в этой блоге.

В настоящее время язык мульти-парадигмы и тот же Петричек в своей книге объясняет различные функциональные методы использовать из C#.

что меня здесь интересует и, следовательно, вопрос,как правильно разделять код.

Итак, я определил структуры данных библиотеки, используя эквивалент дискриминируемых союзов (как показано в книге Петричека), и я проектирую использовать их с неизменяемыми списками и/или кортежами согласно логике домена моих требований.

где я размещаю операции (методы ... функции), который действует на данные структуры?

если я хочу определить функцию высокого порядка, которая использует значение функции, воплощенное в стандартных делегатах Func<T1...TResult>, где я могу его разместить?

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

предполагая, что это правильно, и у меня есть функция высокого порядка:

static class AnimalTopology {
  IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector) {
    // remainder omitted
  }
}

если выборе vertebrated животных есть N конкретные случаи, которые я хочу выставить в библиотеке, какой более правильный способ их выставить.

static class VertebratedSelectorsA {
  // this is compatible with "Func<Skeleton, bool> selector"
  static bool Algorithm1(Skeleton s) {
    //...
  } 
}

или

static class VertebratedSelectorsB {
  // this method creates the function for later application
  static Func<Skeleton, bool> CreateAlgorithm1Selector(Skeleton s) {
     // ...
  } 
}

любая индикация будет очень оценена.

EDIT:

Я хочу процитировать две фразы из т. Petricek, Реальное Функциональное Программирование предисловие Мадса Торгерсена:

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

"правка" -2:

  • Я чувствую, что есть необходимость уточнить вопрос. The функциональное упомянутый в названии строго относится к Функциональное Программирование; Я не прошу больше функциональное способ группировки методов, в смысле более логического способа или способа, который делает больше смысла вообще.

  • это означает, что реализация будет стараться следовать как можно более основополагающим концепциям FP, обобщенным нееет манифест и приведены здесь для удобства и ясности:

  1. функции и типы классов
  2. чистота над изменчивостью
  3. композиция над наследованием
  4. функции более высокого порядка над методом отправка
  5. параметры над нулями

вопрос вокруг как макет библиотеки C# написал следующие концепции FP, поэтому (например) это абсолютно не вариант размещения методов внутри структуры данных; потому что это основополагающая объектно-ориентированная парадигма.

EDIT-3:

также, если вопрос получил ответ (и различные комментарии), я не хочу создавать неправильное впечатление, что сказал,что одна парадигма программирования превосходит другую. Как и прежде, я упомяну об авторитете по FP, Доне Сайме, в его книге Expert F# 3.0 (гл.20-проектирование библиотек F# - стр.565):

[...] Это распространенное заблуждение, что функциональные и OO методологии программирования конкурируют; на самом деле, они в основном ортогональны. [...]

2 ответов


Примечание: если вы хотите более короткий, более-к-пункту ответ, см. Мой другой ответ. Я знаю, что этот здесь может кажется, бродить и идти дальше навсегда и говорить мимо вашей проблемы, но, возможно, это даст вам несколько идей.

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

сначала я попытаюсь вывести несколько вещей из вашего кода:

static class AnimalTopology
{
    // Note: I made this function `static`... or did you omit the keyword on purpose?
    static IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector)
    {
        …
    }
}
  1. если вы разработали эту функцию в соответствии с функциональными принципами, она не должна иметь побочных эффектов. То есть его вывод зависит только от его аргументов. (И в полу-объектно-ориентированное задание, возможно, на другие статические члены AnimalTopology; но так как вы ничего не показали, давайте проигнорируем это возможность.)

  2. если функция действительно свободна от побочных эффектов (и не имеет доступа к статическим членам AnimalTopology), то сигнатура типа функции предполагает, что можно получить Animal С Skeleton, потому что он принимает то, что действует на Skeletons и возвращает Animals.

  3. если это также верно, то позвольте мне предположить следующее ради возможности дать ответ:

    class Skeleton
    {
        …
        public Animal Animal { get { … } } // Skeletons have animals!? We'll get to that.
    }
    

теперь очевидно, что функцию невозможно реализовать, так как это может вывести AnimalС Skeletons, но он не получает никаких Skeleton вообще; он получает только функцию предиката, которая действует на Skeleton. (Вы можете исправить это, добавив второй параметр типа Func<IEnumerable<Skeleton>> getSkeletons, но...)

на мой взгляд, что-то вроде следующего бы больше смысла:

static IEnumerable<Animal> GetVertebrates(this IEnumerable<Skeleton> skeletons,
                                          Func<Skeleton, bool> isVertebrate)
{
    return skeletons
           .Where(isVertebrate)
           .Select(s => s.Animal);
}

теперь можно задаться вопросом почему вы угадываете животных из их скелетов; и это не bool свойство "является ли позвоночное" неотъемлемым свойством животного (или скелета)? Есть ли действительно несколько способов решить это?

я бы предложил следующее:

class Animal
{
     Skeleton Skeleton { get; } // not only vertebrates have skeletons! 
}

class Vertebrate : Animal { … } // vertebrates are a kind of animal 

static class AnimalsExtensions
{
    static IEnumerable<Vertebrate> ThatAreVertebrates(this IEnumerable<Animal> animals)
    {
        return animals.OfType<Vertebrate>();
    }
}

обратите внимание на использование методов расширения выше. Вот пример, как его использовать:

List<Animal> animals = …;
IEnumerable<Vertebrate> vertebrates = animals.ThatAreVertebrates();

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

interface IVertebrateSelectionAlgorithm
{
    IEnumerable<Vertebrate> GetVertebrates(IEnumerable<Animal> animals);
}

это имеет то преимущество ,что его можно настроить / параметризовать, например, с помощью конструктора класса; и вы можете разделить алгоритм на несколько методов, которые все находятся в одном классе (но все private за исключением GetVertebrates.)

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


где я размещаю операции (методы ... функции), который действует на структуры данных?

Я вижу четыре общих подхода (в определенном порядке):

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

  • поместите функции внутри их собственных обозначенных "классы"алгоритм. (Это кажется разумным, когда функции выполняют большую или сложную работу, или должны быть параметризованы/ настроены, или где вы можете разделить алгоритм на несколько функций, которые вы можете логически "сгруппировать" вместе, поместив их в класс тип.)

  • включить функции в лямбда (а.к.а. анонимные делегаты, замыкания и т. д.). (Это хорошо работает, если они маленькие, и вам нужны только в одном определенном месте; код не будет легко повторно использоваться в другом месте.)

  • поместите функции в статический класс и сделайте их методы расширения. (Так работает LINQ to Objects. Это гибридный функциональный и объектно-ориентированный подход. Это требует некоторых дополнительная забота, чтобы получить проблему обнаруживаемости / пространства имен правильно. Многие люди подумают, что этот подход нарушает "инкапсуляцию", когда заходит слишком далеко. Для контраргумента прочитайте отличную статью c++ "Как Функции, Не Являющиеся Членами, Улучшают Инкапсуляцию"; замените "метод расширения" на "функцию друга, не являющегося членом".)

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