F# 函数式编程之 - 一个例子
经过本系列前面几篇文章对 F# 的介绍,是时候来一个比较小巧的例子了。
这个例子的原文见 https://fsharpforfunandprofit.com/posts/roman-numerals/
将罗马数字转换成普通的十进制数字,完整代码如下:
module Roman =
type Digit = I | V | X | L | C | D | M
type Numeral = Numeral of Digit list
let digitToInt =
function
| I -> 1
| V -> 5
| X -> 10
| L -> 50
| C -> 100
| D -> 500
| M -> 1000
let rec digitsToInt =
function
| [] -> 0
| x::y::tail when x < y ->
(digitToInt y - digitToInt x) + digitsToInt tail
| digit::tail ->
digitToInt digit + digitsToInt tail
let print digits = digits |> digitsToInt |> printfn "%A"
非常优雅,非常简洁、清晰,可读性强,易扩展易维护,没有变量,不用管理状态,函数没有副作用,不容易出错,而且类型安全,可进行静态类型分析。
上面是一个模块,可以这样使用它:
open type Roman.Digit
Roman.print [I;I;I;I] // 4
Roman.print [I;V] // 4
Roman.print [V;I] // 6
Roman.print [I;X] // 9
[M;C;M;L;X;X;I;X] |> Roman.print // 1979
[M;C;M;X;L;I;V] |> Roman.print // 1944
本文介绍了一个比较完整的例子,它像一个小点心,希望你也能和我一样初尝 F# 函数式编程的美味。
更新,补充
上面的例子,我说它易扩展易维护,下面我们就对它进行一次小小的扩展试试。
也许你已经发现,上面的例子,输入的参数是一个数组(列表),而不是一个字符串,这让输入很不方便。下面,我们让它能接受字符串。
module Roman =
type Digit = I | V | X | L | C | D | M
type Numeral = Numeral of Digit list
let digitToInt =
function
| I -> 1
| V -> 5
| X -> 10
| L -> 50
| C -> 100
| D -> 500
| M -> 1000
// Digit list -> int
let rec digitsToInt =
function
| [] -> 0
| x::y::tail when x < y ->
(digitToInt y - digitToInt x) + digitsToInt tail
| digit::tail ->
digitToInt digit + digitsToInt tail
// Numeral -> int
// 注意,这里对 Numeral 进行了 unpacking, 即从一个 Numeral 里拆出一个 digits 来。
let toInt (Numeral digits) = digitsToInt digits
type ParsedChar =
| Good of Digit
| Bad of char
let parseChar =
function
| 'I' -> Good I
| 'V' -> Good V
| 'X' -> Good X
| 'L' -> Good L
| 'C' -> Good C
| 'D' -> Good D
| 'M' -> Good M
| ch -> Bad ch
// string -> ParsedChar list
let toDigitList (s:string) =
s.ToCharArray()
|> List.ofArray
|> List.map parseChar
// string -> Numeral
let toNumeral s =
toDigitList s
|> List.choose (
function
| Good digit -> Some digit
| Bad ch ->
eprintfn "%c is not a valid character" ch
None
)
|> Numeral
let print s =
s |> toNumeral |> toInt |> printfn "%A"
可见,原来的代码几乎全部照原样保留,直接添加处理字符串的代码即可,然后新增的函数可以非常轻松地调用原有的函数。我们可以这样使用它:
open type Roman.Digit
Roman.print "IIII"
Roman.print "IV"
Roman.print "VI"
Roman.print "IX"
"MCMLXXIX" |> Roman.print
"MCMXLIV" |> Roman.print
"" |> Roman.print
"IIKKMM" |> Roman.print
这个例子还可以继续扩充/改造,因为现在它还不能处理错误的罗马数字,有兴趣的同学请看原文 https://fsharpforfunandprofit.com/posts/roman-numerals/ 我在这里就不补充完整了。

浙公网安备 33010602011771号