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.DigitRoman.print [I;I;I;I] // 4Roman.print [I;V] // 4Roman.print [V;I] // 6Roman.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.DigitRoman.print "IIII"Roman.print "IV"Roman.print "VI"Roman.print "IX""MCMLXXIX" |> Roman.print"MCMXLIV" |> Roman.print"" |> Roman.print"IIKKMM" |> Roman.print
这个例子还可以继续扩充/改造,因为现在它还不能处理错误的罗马数字。
赞 (0)