分享

F# 函数式编程之 - 一个例子

 悦光阴 2021-03-11

经过本系列前面几篇文章对 F# 的介绍,是时候来一个比较小巧的例子了。

这个例子的原文见 https:///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

这个例子还可以继续扩充/改造,因为现在它还不能处理错误的罗马数字。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多