如何正确划分C#函数库中的代码?
作为一个前提,关于可重用库的FP设计的一个关键区别(就我所学到的),就是这些库比相应的OO(一般而言)更以数据为中心 这似乎也从新兴的技术中得到了证实,如TFD(类型优先开发),Tomas Petricek在博客文章中对此做了很好的解释 如今,语言是多范式的,同一个Petricek在其书中解释了从C#可用的各种函数技术 我感兴趣的是如何正确地对代码进行分区 因此,我已经定义了库数据结构,使用了等效的有区别的联合(如Petricek书中所示),并根据我的需求的域逻辑,将它们与不可变列表和/或元组一起使用 在哪里放置作用于数据结构的操作(方法…函数) 如果要定义使用标准委托如何正确划分C#函数库中的代码?,c#,functional-programming,C#,Functional Programming,作为一个前提,关于可重用库的FP设计的一个关键区别(就我所学到的),就是这些库比相应的OO(一般而言)更以数据为中心 这似乎也从新兴的技术中得到了证实,如TFD(类型优先开发),Tomas Petricek在博客文章中对此做了很好的解释 如今,语言是多范式的,同一个Petricek在其书中解释了从C#可用的各种函数技术 我感兴趣的是如何正确地对代码进行分区 因此,我已经定义了库数据结构,使用了等效的有区别的联合(如Petricek书中所示),并根据我的需求的域逻辑,将它们与不可变列表和/或元组一
Func
中包含的函数值的高阶函数,应将其放置在何处
常识告诉我应该在静态类中对这些方法进行分组,但我希望已经用C#编写函数库的人对此进行确认
假设这是正确的,我有一个像这样的高阶函数:
static class AnimalTopology {
IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector) {
// remainder omitted
}
}
静态类动物拓扑{
Mads Torgersen的《IEnumerable前言》:
[…]您可以在C#中使用函数式编程技术,从而获得巨大的好处,
虽然在F#中这样做更容易、更自然。
[...]
函数式编程是一种思维状态。[…]
编辑-2:
- 我觉得有必要进一步澄清这个问题。标题中提到的功能性严格地涉及到;我不是在问分组方法的更功能性的方式,从更逻辑的意义上,或者从总体上说更有意义的方式
- 这意味着实施将尽可能遵循《宣言》中总结的、为方便和清晰起见在此引用的FP的基本概念:
类上的函数和类型
纯度高于易变性
组合重于继承
方法调度上的高阶函数
空值选项
问题在于如何布局遵循FP概念编写的C#库,因此(例如)将方法放入数据结构中绝对不是一个选项;因为这是一个面向对象的范例
编辑-3:
另外,如果这个问题得到了回应(以及各种评论),我不想给人一种错误的印象,即有人说一种编程范式优于另一种编程范式。
如前所述,我将在《F#3.0专家》(第20章-设计F#库-第565页)一书中提到FP的权威Don Syme:
[…]函数式编程方法和面向对象编程方法相互竞争是一种常见的误解;事实上,它们在很大程度上是正交的
注意:如果你想要一个更简短、更切题的答案,请参阅我的另一个答案。我知道这里的这一个答案似乎是漫无边际的,永远继续下去,谈论你的问题,但也许它会给你一些想法
如果不知道动物
和骨骼
之间的确切关系,就很难回答您的问题。我将在回答的后半部分对这一关系提出建议,但在此之前,我将简单地赞同我在您的帖子中看到的内容
首先,我将尝试从您的代码中推断出一些东西:
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)
{
…
}
}
现在很明显,您的函数不可能实现,因为它可以从骨架
s派生动物
s,但它根本不接收任何骨架
;它只接收作用于骨架
的谓词函数。(您可以通过添加第二个Func getSkeletons
类型的参数来修复此问题,但是…)
在我看来,下面这样的说法更有意义:
static IEnumerable<Animal> GetVertebrates(this IEnumerable<Skeleton> skeletons,
Func<Skeleton, bool> isVertebrate)
{
return skeletons
.Where(isVertebrate)
.Select(s => s.Animal);
}
请注意上面扩展方法的使用。下面是一个如何使用它的示例:
List<Animal> animals = …;
IEnumerable<Vertebrate> vertebrates = animals.ThatAreVertebrates();
列出动物=…;
i可数脊椎动物=动物。它们是脊椎动物();
现在假设您的扩展方法做了更复杂的工作。在这种情况下,最好将其放入自己指定的“算法类型”中:
接口IverteRate选择算法
{
IEnumerable脊椎动物(IEnumerable动物);
}
这样做的好处是,它可以设置/参数化,例如通过类构造函数;您可以将算法拆分为多个方法,这些方法都位于同一个类中(但都是私有的,除了Get脊椎动物)
当然,你也可以用函数闭包进行同样的参数化,但根据我的经验,在C#设置中很快就会变得混乱。在这里,类是将一组函数作为一个逻辑实体组合在一起的好方法
在哪里放置作用于数据结构的操作(方法…函数)
我看到了四种常见的方法(没有特定的顺序):
- 将函数放入数据结构中。(这是面向对象的“方法”方法。当函数仅作用于该类型的实例时,这种方法是合适的。例如,当函数“并拢”时,这种方法可能不太合适几个不同类型的对象,并吐出另一个类型的对象。在这种情况下,我会…)
- 将函数放入它们自己指定的“算法类”(当函数做了大量或复杂的工作,或者需要参数化/配置,或者您可能希望将算法拆分为多个函数时,这似乎是合理的,然后您可以通过将它们放在一个类类型中逻辑地“分组”在一起。)
- 转函数
class Skeleton
{
…
public Animal Animal { get { … } } // Skeletons have animals!? We'll get to that.
}
static IEnumerable<Animal> GetVertebrates(this IEnumerable<Skeleton> skeletons,
Func<Skeleton, bool> isVertebrate)
{
return skeletons
.Where(isVertebrate)
.Select(s => s.Animal);
}
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);
}