Как правильно разбить код на разделы в функциональной библиотеке 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, обобщенным нееет манифест и приведены здесь для удобства и ясности:
- функции и типы классов
- чистота над изменчивостью
- композиция над наследованием
- функции более высокого порядка над методом отправка
- параметры над нулями
вопрос вокруг как макет библиотеки 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)
{
…
}
}
если вы разработали эту функцию в соответствии с функциональными принципами, она не должна иметь побочных эффектов. То есть его вывод зависит только от его аргументов. (И в полу-объектно-ориентированное задание, возможно, на другие статические члены
AnimalTopology
; но так как вы ничего не показали, давайте проигнорируем это возможность.)если функция действительно свободна от побочных эффектов (и не имеет доступа к статическим членам
AnimalTopology
), то сигнатура типа функции предполагает, что можно получитьAnimal
СSkeleton
, потому что он принимает то, что действует наSkeleton
s и возвращаетAnimal
s.-
если это также верно, то позвольте мне предположить следующее ради возможности дать ответ:
class Skeleton { … public Animal Animal { get { … } } // Skeletons have animals!? We'll get to that. }
теперь очевидно, что функцию невозможно реализовать, так как это может вывести Animal
С Skeleton
s, но он не получает никаких 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++ "Как Функции, Не Являющиеся Членами, Улучшают Инкапсуляцию"; замените "метод расширения" на "функцию друга, не являющегося членом".)
Примечание: я мог бы перейти к каждому из них более подробно, если люди хотят, но прежде чем я это сделаю, я подожду и посмотрите, какую обратную связь получает этот ответ.