关于可重用库(针对我正在学习的内容)的FP设计的主要区别之一是,这些库比对应的OO(通常)更以数据为中心。
Tomas Petricek在this博客文章中对此进行了很好的解释,这似乎也从 TFD (类型优先开发)等新兴技术中得到了证实。
如今,语言是多范式的,其书中的相同Petricek解释了可用于C#的各种功能技术。
我在这里感兴趣的是,因此是一个问题,即如何正确分割代码。
因此,我使用等效的联合定义了库数据结构(如Petricek书中所示),并且我计划根据矿山需求的领域逻辑将它们与不可变的列表和/或元组一起使用。
我应该在哪里放置对数据结构起作用的操作(方法...函数)?
如果我想定义一个使用标准委托(delegate)Func<T1...TResult>
中包含的函数值的高阶函数,该将其放置在哪里?
常识说我可以将这些方法归为静态类,但是我想得到已经用C#编写过函数库的人们的确认。
假设这是正确的,并且我有一个像这样的高阶函数:
static class AnimalTopology {
IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector) {
// remainder omitted
}
}
如果选择脊椎动物要在库中显示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) {
// ...
}
}
任何指示将不胜感激。
编辑:
我想引用T. Petricek的两个短语,Mads Torgersen的Real World Functional Programming前言:
EDIT-2:
问题围绕,如何布局遵循FP概念编写的C#库,因此(例如)绝对不是将方法放入数据结构中的选择。因为这是一个创始的面向对象范例。
EDIT-3:
同样,如果问题得到了回应(以及各种评论),我也不想给人以错误的印象,即曾经有人说一种编程范例比另一种编程范例优越。
如前所述,我将在FP F 3.0版专家书(第20章-设计F#库-第565页)中提到FP的一个权限:Don Syme:
最佳答案
不知道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
。 class Skeleton
{
…
public Animal Animal { get { … } } // Skeletons have animals!? We'll get to that.
}
现在很明显,您的函数无法实现,因为它可以从
Animal
派生Skeleton
,但是它根本不接收任何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#设置中很快会变得困惑。在这里,类是将一组功能组合在一起作为一个逻辑实体的好方法。