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)

相关推荐