如何正确划分C#函数库中的代码?

如何正确划分C#函数库中的代码?,c#,functional-programming,C#,Functional Programming,作为一个前提,关于可重用库的FP设计的一个关键区别(就我所学到的),就是这些库比相应的OO(一般而言)更以数据为中心 这似乎也从新兴的技术中得到了证实,如TFD(类型优先开发),Tomas Petricek在博客文章中对此做了很好的解释 如今,语言是多范式的,同一个Petricek在其书中解释了从C#可用的各种函数技术 我感兴趣的是如何正确地对代码进行分区 因此,我已经定义了库数据结构,使用了等效的有区别的联合(如Petricek书中所示),并根据我的需求的域逻辑,将它们与不可变列表和/或元组一

作为一个前提,关于可重用库的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);
      }