如何在C#函数库中正确分区代码?

jay*_*jay 9 c# functional-programming

作为关于可重用库(对于我正在学习的)的FP设计的关键差异的前提之一,这些是更加以数据为中心的相应OO(通常).

这似乎也得到了TFD(Type-First-Development)等新兴技术的证实,Tomas Petricek在这篇博客文章中对此进行了很好的解释.

如今语言是多范式的,Petricek在其书中解释了可用于C#的各种功能技术.

我在这里感兴趣,因此问题是如何正确地划分代码.

所以我已经定义了库数据结构,使用相当于被区分的联合(如Petricek书中所示),并且我计划根据我的需求的域逻辑将它们与不可变列表和/或元组一起使用.

我在哪里放置对数据结构起作用的操作(方法......函数)?

如果我想要定义一个使用标准委托中包含的函数值的高阶函数,Func<T1...TResult>我在哪里放置它?

常识告诉我要在静态类中对这些方法进行分组,但我希望得到已经在C#中编写函数库的人的确认.

假设这是正确的,我有一个这样的高阶函数:

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

如果选择脊椎动物有N个我希望在库中暴露的特殊情况,那么暴露它们的方法更正确.

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

要么

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

任何迹象将非常感激.

编辑:

我想引用来自Mads Torgersen的真实世界功能编程前言T. Petricek的两个短语:

[...]你可以在C#中使用函数式编程技术,但是在F#中这样做更容易,更自然.[...]功能编程是一种心态.[...]

EDIT 2:

  • 我觉得有必要进一步澄清这个问题.标题中提到的功能严格地与功能编程有关 ; 我不是要求更具功能性的分组方法,从更多的逻辑方式或更一般的方式来看.

  • 这意味着实施将尝试尽可能地遵循由NOOO宣言总结的FP的概念,并在此引用以方便和清楚:

  1. 类上的函数和类型
  2. 纯度超过可变性
  3. 继承的构成
  4. 方法调度的高阶函数
  5. 空值选项

问题在于如何布局一个按照FP概念编写的C#库,所以(例如)它绝对不是将方法放在数据结构中的选项; 因为这是一个基于面向对象的范式.

编辑-3:

此外,如果问题得到回应(以及各种评论),我不想给人一种错误的印象,即曾经说过一种编程范式优于另一种编程范式.和以前一样,我会在其专着F#3.0(第20章 - 设计F#图书馆 - 第565页)中提到FP的权威,Don Syme:

[...]一种常见的误解是功能和OO编程方法竞争; 事实上,它们基本上是正交的.[...]

sta*_*ica 3

\n

注:如果您想要更简短、更切题的答案,请参阅我的另一个答案。我知道这里可能看起来很漫无目的,并且一直在谈论您的问题,但也许它会给您一些想法。

\n
\n\n

Animal如果不知道和之间的确切关系,很难回答你的问题Skeleton。我将在我的答案的后半部分提出关于这种关系的建议,但在此之前,我将简单地同意我在你的帖子中看到的内容。

\n\n

首先,我将尝试从您的代码中推断出一些内容:

\n\n
static class AnimalTopology\n{\n    // Note: I made this function `static`... or did you omit the keyword on purpose?\n    static IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector)\n    {\n        \xe2\x80\xa6\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  1. 如果您根据功能原理设计了该功能,那么它应该没有副作用。也就是说,它的输出仅依赖于它的参数。(在半面向对象的设置中,也许在AnimalTopology; 的其他静态成员上,但由于您没有显示任何内容,让我们忽略这种可能性。)

  2. \n
  3. 如果该函数确实没有副作用(并且不访问 的静态成员AnimalTopology),则该函数的类型签名表明可以Animal从 a派生 an Skeleton,因为它接受作用于Skeletons 的内容并返回Animals 。

  4. \n
  5. 如果这也是真的,那么为了能够给出答案,让我假设以下内容:

    \n\n
    class Skeleton\n{\n    \xe2\x80\xa6\n    public Animal Animal { get { \xe2\x80\xa6 } } // Skeletons have animals!? We\'ll get to that.\n}\n
    Run Code Online (Sandbox Code Playgroud)
  6. \n
\n\n

现在很明显你的函数是不可能实现的,因为它可以AnimalSkeletons 派生 s,但它根本不接收任何东西Skeleton;它只接收作用于 a 的谓词函数Skeleton。(您可以通过添加第二个类型的参数来解决此问题Func<IEnumerable<Skeleton>> getSkeletons,但是......)

\n\n

在我看来,像下面这样的东西会更有意义:

\n\n
static IEnumerable<Animal> GetVertebrates(this IEnumerable<Skeleton> skeletons,\n                                          Func<Skeleton, bool> isVertebrate)\n{\n    return skeletons\n           .Where(isVertebrate)\n           .Select(s => s.Animal);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

现在,人们可能想知道为什么你要根据骨骼来猜测动物?“是脊椎动物”这个属性难道不是bool动物(或骨骼)的固有属性吗?真的有几种方法可以决定吗?

\n\n

我建议如下:

\n\n
class Animal\n{\n     Skeleton Skeleton { get; } // not only vertebrates have skeletons! \n}\n\nclass Vertebrate : Animal { \xe2\x80\xa6 } // vertebrates are a kind of animal \n\nstatic class AnimalsExtensions\n{\n    static IEnumerable<Vertebrate> ThatAreVertebrates(this IEnumerable<Animal> animals)\n    {\n        return animals.OfType<Vertebrate>();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意上面扩展方法的使用。这是如何使用它的示例:

\n\n
List<Animal> animals = \xe2\x80\xa6;\nIEnumerable<Vertebrate> vertebrates = animals.ThatAreVertebrates();\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

现在假设您的扩展方法做了更复杂的工作。在这种情况下,将其放入自己指定的“算法类型”中可能是个好主意:

\n\n
interface IVertebrateSelectionAlgorithm\n{\n    IEnumerable<Vertebrate> GetVertebrates(IEnumerable<Animal> animals);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这样做的优点是可以通过类构造函数来设置/参数化;您可以将算法拆分为多个方法,这些方法都位于同一类中(但除了 之外privateGetVertebrates

\n\n

当然,您可以使用函数闭包执行相同类型的参数化,但根据我的经验,这在 C# 设置中很快就会变得混乱。在这里,类是将一组函数组合在一起作为一个逻辑实体的好方法。

\n