Comparing F# with C#: A simple sum

https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-sum-of-squares.html

F# 与 C# 的比较:一个简单的求和

要了解一些真正的 F# 代码是什么样的,让我们从一个简单的问题开始:“对 1 到 N 的平方求和”。

我们将比较一个F#实现和一个C#实现。首先是F#的代码:

// define the square function
let square x = x * x

// define the sumOfSquares function
let sumOfSquares n = 
   [1..n] |> List.map square |> List.sum

// try it
sumOfSquares 100

这个看起来很神秘的|>被称为管道操作符。它只是把一个表达式的输出连接到下一个表达式的输入。所以sumOfSquares的代码是这样工作的:

  1. 创建一个 1 到 n 的列表(方括号构成一个列表)。

  2. 将该列表输送到名为List.map的库函数中,使用我们刚刚定义的 "square "函数将输入列表转换为输出列表。

  3. 将生成的正方形列表通过管道传输到名为 List.sum 的库函数中。你能猜出它的作用吗?

  4. 没有明确的“返回”声明。 List.sum 的输出是函数的整体结果。

接下来,这是一个使用基于C语言的经典(非函数式)风格的C#实现。(后面将讨论使用LINQ的更多功能版本)。

public static class SumOfSquaresHelper
{
   public static int Square(int i)
   {
      return i * i;
   }

   public static int SumOfSquares(int n)
   {
      int sum = 0;
      for (int i = 1; i <= n; i++)
      {
         sum += Square(i);
      }
      return sum;
   }
}

有什么区别?

  • F#代码更紧凑

  • F# 代码没有任何类型声明

  • F#可以交互式开发

让我们依次来看这些。

更少的代码

最明显的区别是,C#代码多了很多。13行C#代码,而F#代码只有3行(忽略注释)。C#代码有很多 "噪音",比如大括号、分号等等。而且在C#中,这些函数不能独立存在,而是需要添加到一些类中("SumOfSquaresHelper")。F#使用空格代替小括号,不需要行结束符,而且函数可以独立存在。

在 F# 中,将整个函数写在一行中是很常见的,就像“square”函数一样。 sumOfSquares 函数也可以写在一行中。在 C# 中,这通常被认为是不好的做法。

当函数确实有多行时,F# 使用缩进来指示代码块,这样就不需要大括号了。 (如果你曾经使用过 Python,这是相同的想法)。所以 sumOfSquares 函数也可以这样写:

let sumOfSquares n = 
   [1..n] 
   |> List.map square 
   |> List.sum

唯一的缺点是您必须小心地缩进代码。就个人而言,我认为值得trade-off的.

没有类型声明

下一个区别是 C# 代码必须显式声明所有使用的类型。例如,int i 参数和 int SumOfSquares 返回类型。是的,C# 确实允许您在许多地方使用“var”关键字,但不能用于函数的参数和返回类型。

在 F# 代码中,我们根本没有声明任何类型。这是很重要的一点:F# 看起来像一种无类型语言,但实际上它与 C# 一样类型安全,事实上,甚至更安全! F# 使用一种称为“类型推断”的技术从上下文中推断您正在使用的类型。它在大多数时候都工作得非常好,并且极大地降低了代码的复杂性。

在这种情况下,类型推断算法注意到我们从一个整数列表开始。这反过来意味着平方函数和求和函数也必须采用整数,并且最终值必须是整数。您可以通过在交互窗口中查看编译结果来了解推断的类型。你会看到类似的东西:

val square : int -> int

这意味着“square”函数接受一个 int 并返回一个 int。

如果原始列表使用浮点数代替,则类型推断系统会推断 square 函数使用浮点数代替。试试看:

// define the square function
let squareF x = x * x

// define the sumOfSquares function
let sumOfSquaresF n = 
   [1.0 .. n] |> List.map squareF |> List.sum  // "1.0" is a float

sumOfSquaresF 100.0

类型检查非常严格!如果您尝试在原始 sumOfSquares 示例中使用浮点列表 ([1.0..n]),或在 sumOfSquaresF 示例中使用整数列表 ([1..n]),您将从编译器中得到类型错误。

交互开发

最后,F# 有一个交互式窗口,您可以在其中立即测试代码并试用它。在 C# 中,没有简单的方法可以做到这一点。

例如,我可以编写我的 square 函数并立即对其进行测试:

// define the square function
let square x = x * x

// test
let s2 = square 2
let s3 = square 3
let s4 = square 4

当我对它的效果满意时,我可以继续写下一段代码。

这种交互性鼓励一种渐进的编码方法,这种方法可能会让人上瘾!

此外,许多人声称,交互式地设计代码可以强制执行良好的设计实践,如解耦和明确的依赖关系,因此,适合交互式评估的代码也将是容易测试的代码。反之,不能进行交互式测试的代码可能也会很难测试。

重新审视 C# 代码

我最初的例子是用 "老式 "C#写的。C#已经融入了很多功能特性,使用LINQ扩展可以以更紧凑的方式重写这个例子。

所以这是另一个 C# 版本 —— F# 代码的逐行翻译。

public static class FunctionalSumOfSquaresHelper
{
   public static int SumOfSquares(int n)
   {
      return Enumerable.Range(1, n)
         .Select(i => i * i)
         .Sum();
   }
}

但是,除了花括号和句点和分号的干扰之外,C# 版本还需要声明参数和返回类型,这与 F# 版本不同。

许多C#开发者可能会发现这是一个微不足道的例子,但当逻辑变得更加复杂时,他们还是会求助于循环。但在F#中,你几乎不会看到像这样的显式循环。例如,请看这篇关于从更复杂的循环中消除模板的文章

Last updated