首页 > 代码库 > F#之旅3 - F# PK C#:简单的求和
F#之旅3 - F# PK C#:简单的求和
原文链接:https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/fvsc-sum-of-squares.html
Comparing F# with C#: A simple sum
F# PK C#:简单的求和
To see what some real F# code looks like, let‘s start with a simple problem: "sum the squares from 1 to N".
We‘ll compare an F# implementation with a C# implementation. First, the F# code:
// 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
我们以“计算从1到n的平方的和”这个简单问题开始,来看一下实际的F#代码。
我们将比较F#实现和C#实现的不同。首先,F#如下:
// 定义一个求平方的函数
let square x = x * x
// 定义一个求平方和的函数
let sumOfSquares n =
[1..n] |> List.map square |> List.sum
// 试一下效果
sumOfSquares 100
The mysterious looking |> is called the pipe operator. It just pipes the output of one expression into the input of the next. So the code for sumOfSquares reads as:
1、Create a list of 1 to n (square brackets construct a list).
2、Pipe the list into the library function called List.map, transforming the input list into an output list using the "square" function we just defined.
3、Pipe the resulting list of squares into the library function called List.sum. Can you guess what it does?
4、There is no explicit "return" statement. The output of List.sum is the overall result of the function.
|>,这个神秘的东东叫做管道操作符。它做的就是把一个表达式的输出传到另一个表达式的输入。所以,这些代码的意思是:
1、创建一个从1到n的列表(中括号来创建列表)。
2、将列表传(流)到叫做List.map的库函数,使用我们刚刚定义的“平方”函数来进行从输入到输出的转换。
3、将得到的平方值的列表传到叫做List.sum的库函数。你猜下它做了啥?
4、这里没有显式的“返回”说明。List.sum的输出就是这个函数的全部结果输出了。
Next, here‘s a C# implementation using the classic (non-functional) style of a C-based language. (A more functional version using LINQ is discussed later.)
接下来,是一份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;
}
}
What are the differences?
The F# code is more compact
The F# code didn‘t have any type declarations
F# can be developed interactively
Let‘s take each of these in turn.
有什么区别?
F#的代码更简洁
F#的代码没有任何类型声明
F#能交互式的开发
我们来逐条的理解。
Less code
The most obvious difference is that there is a lot more C# code. 13 C# lines compared with 3 F# lines (ignoring comments). The C# code has lots of "noise", things like curly braces, semicolons, etc. And in C# the functions cannot stand alone, but need to be added to some class ("SumOfSquaresHelper"). F# uses whitespace instead of parentheses, needs no line terminator, and the functions can stand alone.
In F# it is common for entire functions to be written on one line, as the "square" function is. The sumOfSquares function could also have been written on one line. In C# this is normally frowned upon as bad practice.
When a function does have multiple lines, F# uses indentation to indicate a block of code, which eliminates the need for braces. (If you have ever used Python, this is the same idea). So the sumOfSquares function could also have been written this way:
let sumOfSquares n =
[1..n]
|> List.map square
|> List.sum
The only drawback is that you have to indent your code carefully. Personally, I think it is worth the trade-off.
更少的代码
很明显,最大的区别是C#的代码要多得多。13行C#代码对应3行F#代码(不包含注释)。C#的代码有很多“杂音”,比如大括号、分号等等。并且C#的函数不能单独的存在,必须放在一个类里面。F#用空格代替了括号的左右,不需要行尾终结符,并且函数可以独立存在。
在F#里,经常可以像“square”函数一样,把整个函数写成一行。“sumOfSquares”函数也可以写成一行。在F#里,这样做就是不被认可的坏习惯了。
当一个函数有多行时,F#用缩进来凸出一个代码块,而不需要额外的大括号。(如果你用过python,会发现这点和python一样)。因此,“sumOfSquares”函数也可以写成这样:
let sumOfSquares n =
[1..n]
|> List.map square
|> List.sum
唯一的缺点是需要小心你的缩进。个人认为,这样也挺好的。
No type declarations
The next difference is that the C# code has to explicitly declare all the types used. For example, the int i parameter and int SumOfSquares return type. Yes, C# does allow you to use the "var" keyword in many places, but not for parameters and return types of functions.
In the F# code we didn‘t declare any types at all. This is an important point: F# looks like an untyped language, but it is actually just as type-safe as C#, in fact, even more so! F# uses a technique called "type inference" to infer the types you are using from their context. It works amazingly very well most of the time, and reduces the code complexity immensely.
In this case, the type inference algorithm notes that we started with a list of integers. That in turn implies that the square function and the sum function must be taking ints as well, and that the final value must be an int. You can see what the inferred types are by looking at the result of the compilation in the interactive window. You‘ll see something like:
val square : int -> int
which means that the "square" function takes an int and returns an int.
没有类型声明
第二个区别,是C#代码显示的声明了所有用到的类型。例如,参数i是int类型,函数SumOfSquares的返回类型也是int。是的,C#也允许在很多地方用“var”关键字类自动推导类型,但是函数参数和函数返回值用不了。
在F#代码里,我们不需要声明所有类型。有一点很重要:F#看起来像是无类型(弱类型)语言,但它确实和C#一样是类型安全的,千真万确!F#用了一种叫做“类型推导”的技术来从上下文推断出类型。大部分时候它工作得出奇的好,极大的减少了代码的复杂性。
在这里,类型推导算法记录到:从一个整型列表开始。那么,又意味着square函数和sum函数必须能接受整型数值作为参数,并且得到的结果也要是整型。如果在交互窗口写代码,你可以直接在窗口里看到推导出来的类型。你会看到类似这样的东西:
val square : int -> int
意思是square函数接受一个整型,并且返回一个整型。
If the original list had used floats instead, the type inference system would have deduced that the square function used floats instead. Try it and see:
// 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
The type checking is very strict! If you try using a list of floats ([1.0..n]) in the original sumOfSquares example, or a list of ints ([1 ..n]) in the sumOfSquaresF example, you will get a type error from the compiler.
如果初始列表里用浮点型代替整型,类型推导系统也能推导出:square用浮点型输入输出。
...
类型检查是非常严格的!如果你尝试输入浮点型列表到之前的sumOfSquares,或者将整型的列表输入到sumOfSquaresF,编译器都会给你报类型错误。
Interactive development
Finally, F# has an interactive window where you can test the code immediately and play around with it. In C# there is no easy way to do this.
For example, I can write my square function and immediately test it:
// define the square function
let square x = x * x
// test
let s2 = square 2
let s3 = square 3
let s4 = square 4
When I am satisfied that it works, I can move on to the next bit of code.
This kind of interactivity encourages an incremental approach to coding that can become addictive!
Furthermore, many people claim that designing code interactively enforces good design practices such as decoupling and explicit dependencies, and therefore, code that is suitable for interactive evaluation will also be code that is easy to test. Conversely, code that cannot be tested interactively will probably be hard to test as well.
交互式开发
最后一点,F#还有一个交互式窗口,可以用来直接输入代码经常测试。C#不容易做到这点。
举个例子,我可以直接写一个square函数并且测试它:
...
当我觉得这个函数让我满意时,我再把它移到别的代码里用。
这样的交互式,让我们可以用增量的方式来写代码,让人上瘾!(我是没感觉的)
此外,很多人声称,把代码设计成可以交互式执行,是一个良好的设计实践,能解耦和明确依赖关系,因此,适合于交互式评审的代码也将是容易测试的代码。反之,无法进行交互测试的代码也会很难测试。
The C# code revisited
My original example was written using "old-style" C#. C# has incorporated a lot of functional features, and it is possible to rewrite the example in a more compact way using the LINQ extensions.
So here is another C# version -- a line-for-line translation of the F# code.
public static class FunctionalSumOfSquaresHelper
{
public static int SumOfSquares(int n)
{
return Enumerable.Range(1, n)
.Select(i => i * i)
.Sum();
}
}
However, in addition to the noise of the curly braces and periods and semicolons, the C# version needs to declare the parameter and return types, unlike the F# version.
Many C# developers may find this a trivial example, but still resort back to loops when the logic becomes more complicated. In F# though, you will almost never see explicit loops like this. See for example, this post on eliminating boilerplate from more complicated loops.
C#代码重写
最初的例子是用“远古风格”的C#写的。C#也包含了很多函数式特性,能用LINQ扩展来把代码重写得更简洁。
下面是另一个C#写的版本,从F#的代码一行行的翻译过来的。
...
然而,大括号和分号之类的“杂音”还在,参数和返回值的类型声明也少不了,还是不像F#版本那么简洁。
一些C#开发可能觉得这个例子不值一提,在遇到复杂的逻辑时,仍然靠循环来做。再看一个例子吧,教你解脱:http://fsharpforfunandprofit.com/posts/conciseness-extracting-boilerplate/
F#之旅3 - F# PK C#:简单的求和