F#到底有什么用?
奇妙游写到第五篇,前面的几篇都是开场白:
- 一个用F#编写WinForm的例子
- donet命令行工具,也就是F#的开发环境
- 关于函数和函数式编程的碎碎念
- 函数式编程的核心概念:值
下面,我们开始正式来搞点事情,看看F#能做些什么。在此之前,我们再复习F#的运行环境。
F# Interactive
那么F#到底有什么用呢?我们前面说了F#有一个命令行,可以用dotnet fsi打开。这个叫做交互式开发环境,比力当代的语言,都会提供一个交互式的环境,比如Java、Python,都有。而函数式的语言,则更加注重这个环境,缘故原由如下:
函数式的编程,注重由下向上来开发,为了实现一个体系的整体功能,先逐步实现更加底层的功能,慢慢把一个体系整合出来。相区别的是面向对象的步伐开发,一开始会投入大量的精力来规划类层次布局、设计类的接口。函数固然也是接口,但是函数的接口很轻。
每一个小函数的精确性比力轻易证明,而精确的函数组合在一起,加上值的概念,整个软件体系的精确性也很轻易保证;
在反复实验和测试函数的过程,就十分有必要有一个可以输个函数得到一个值的盘算器。那些支持F#开发的环境,很轻易就设置一个F# Interactive,比如Rider中Shift+Shift,Start F# Interactive就是这个样子:
在下面的提示符里面就能输入命令,帮助命令输入
#help;;
通过帮助可以看到,F# 交互窗口指令:
- #r "file.dll";; // 引用(动态加载)给定的 DLL
- #i "package source uri";; // 搜索包时包含包源 uri
- #I "path";; // 为被引用的 DLL 添加给定搜索路径
- #load "file.fs" ...;; // 像已编译和被引用的文件一样加载给定的文件
- #time ["on"|"off"];; // 启用/停止计时
- #help;; // 显示帮助
- #r "nuget:FSharp.Data, 3.1.2";; // 加载 Nuget 包 'FSharp.Data' 版本 '3.1.2'
- #r "nuget:FSharp.Data";; // 加载 Nuget 包 'FSharp.Data' 具有最高版本
- #clear;; // 清除屏幕
- #quit;; // 退出
复制代码 帮助还会告诉你,F# 交互窗口命令行选项:
- 请参阅“dotnet fsi --help”以了解各个选项
复制代码 总之,我们能够通过运行上面的命令运行一个开发环境,我们也能通过dotnet fsi filename.fsx来执行一个脚本。
盘算器
第一个作用:当然是高级盘算器。
比如,小朋侪问:1+2+3+…+100等于多少?
马上就有val it: int = 55。这个问题可以口算,但是更大的数字怎么办?你说你还是能口算……那算我没说。
上面这里有个奇怪的东西|>,这是一个运算符(术语:管道),实在很简朴,就是
这么写是因为可以连着写,用|>,比如,100以内所有能被3整除的数和是多少?
- [1..100]
- |> List.filter (fun i -> i % 3 = 0)
- |> List.sum;;
复制代码 得到:
还可以更加复杂:
1 − 1 2 + 1 3 − 1 4 + … 1 - \frac{1}{2} + \frac{1}{3} - \frac{1}{4} + \ldots 1−21+31−41+…
- [1..1000000]
- |> List.map (fun i-> (-1.0) ** (float i+1.0) / float i)
- |> List.sum;;
复制代码 盘算 π \pi π
利用交互式盘算器,可以办理所有小学生的奇怪盘算。下面就来点严肃的,盘算一下 π \pi π。
利用的加拿大滑铁卢大学的Bouweins提出的公式。
y 0 = 2 − 1 , α 0 = 6 − 4 2 y n = 1 − ( 1 − y n − 1 4 ) 1 / 4 1 + ( 1 − y n − 1 4 ) 1 / 4 α n = ( 1 + y n ) 4 α n − 1 − 2 2 n + 3 y n ( 1 + y n + y n 2 ) π = lim n → ∞ 1 α n \begin{split} &y_0 = \sqrt{2}-1, \alpha_0=6-4\sqrt{2}\\ &y_{n} = \frac{1-(1-y_{n-1}^4)^{1/4}}{1+(1-y_{n-1}^4)^{1/4}}\\ &\alpha_n = (1+y_n)^4\alpha_{n-1}-2^{2n+3}y_n(1+y_n+y_n^2)\\ &\pi = \lim_{n\to\infty}\frac{1}{\alpha_n} \end{split} y0=2 −1,α0=6−42 yn=1+(1−yn−14)1/41−(1−yn−14)1/4αn=(1+yn)4αn−1−22n+3yn(1+yn+yn2)π=n→∞limαn1
这个盘算方法非常厉害,只必要迭代15次,精度就能到达20亿位。
那么我们编辑一个fsx文件:
- let sqrt (x: decimal) n =
- let rec _sqrt (x: decimal) (rn: decimal) n =
- match n with
- | 0 -> rn
- | _ -> _sqrt x ((rn + x / rn) * 0.5m) (n - 1)
- _sqrt x (x / 3m) n
- let quad (x: decimal) = sqrt (sqrt x 32) 32
- let sqrt2 = sqrt 2m 32
- let yp (y: decimal) =
- let y' = 1.0m - y * y * y * y
- let y'' = quad y'
- (1m - y'') / (1m + y'')
- let ap (alpha: decimal) (y: decimal) (n: int) =
- let term1 = (1m + y) * (1m + y) * (1m + y) * (1m + y) * alpha
- let term2 = 2.0 ** (2.0 * float n + 1.0)
- let term3 = y * (1m + y + y * y)
- term1 - (decimal term2) * term3
- let alpha n =
- let rec y_a n =
- match n with
- | 0 -> sqrt2 - 1m, 6m - 4m * sqrt2
- | _ ->
- let y_1, alpha_1 = y_a (n - 1)
- let y = yp y_1
- let a = ap alpha_1 y n
- y, a
- let _, a = y_a n
- 1m / a
- seq {0..10}
- |> Seq.map (fun i -> i, alpha i)
- |> Seq.iter (fun (i, pi) -> printfn $"%4i{i}\t%A{pi}")
复制代码 为了得到更多的有效精度,我们接纳了decimal数据,这个数据类型最少可以确保28位的精确盘算。
首先我们定义了一个参数是decimal的开方运算符,因为.NET针对这个数据类型只有加减乘除等运算,没有开方。
接纳迭代法:
x 0 = a / 3 x n + 1 = 1 2 ( x n + a x n ) a = lim n → ∞ x n x_0 = a/3 \\ x_{n+1} = \frac{1}{2}(x_n + \frac{a}{x_n})\\ \sqrt{a} = \lim_{n\to\infty}x_n x0=a/3xn+1=21(xn+xna)a =n→∞limxn
这里使用了尾递归,而不接纳循环。在F#中,尾递归会被编译器优化为循环,以是不用担心性能问题和栈溢出问题。
从下面的代码可以看到,F#在体例步伐时,会把一小段一小段数学形貌实现为一个个函数,如许的代码更加清晰,更加轻易理解。
总结
- F#可以作为一个交互式盘算器,可以办理所有小学生的奇怪盘算。
- 利用F#很直观地实现数学表达式,所接纳的代码不接纳循环、不使用变量,而是接纳递归,这是函数式编程的典型思考方式。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |