F#中的类型与模块

f#

关于类型中的静态字典的困惑的答案,在 F# 中完成了一个建议:and just in general: try to use fewer classes and more modules and functions; they're more idiomatic in F# and lead to fewer problems in general

这是一个很好的观点,但我 30 年的 OO 只是还不想放弃课程(尽管当我们离开 C 时,我疯狂地与 C++ 作斗争......)

所以让我们拿一个实际的现实世界对象:

type Currency =
    {
        Ticker: string
        Symbol: char
    }

and MarginBracket =
    {
        MinSize:           decimal
        MaxSize:           decimal
        Leverage:          int
        InitialMargin:     decimal
        MaintenanceMargin: decimal
    }

and Instrument =
    {
        Ticker:             string
        QuantityTickSize:   int
        PriceTickSize:      int
        BaseCurrency:       Currency
        QuoteCurrency:      Currency
        MinQuantity:        decimal
        MaxQuantity:        decimal
        MaxPriceMultiplier: decimal
        MinPriceMultiplier: decimal
        MarginBrackets:     MarginBracket array
    }

    // formatting
    static member private formatValueNoSign (precision: int) (value: decimal) =
        let zeros = String.replicate precision "0"
        String.Format($"{{0:#.%s{zeros}}}", value)

    static member private formatValueSign (precision: int) (value: decimal) =
        let zeros = String.replicate precision "0"
        String.Format($"{{0:+#.%s{zeros};-#.%s{zeros}; 0.%s{zeros}}}", value)


    member this.BaseSymbol  = this.BaseCurrency.Symbol
    member this.QuoteSymbol = this.QuoteCurrency.Symbol

    member this.QuantityToString    (quantity)          = $"{this.BaseSymbol}{Instrument.formatValueSign    this.QuantityTickSize quantity}"
    member this.PriceToString       (price)             = $"{this.QuoteSymbol}{Instrument.formatValueNoSign this.PriceTickSize price}"
    member this.SignedPriceToString (price)             = $"{this.QuoteSymbol}{Instrument.formatValueSign   this.PriceTickSize price}"
    member this.RoundQuantity       (quantity: decimal) = Math.Round (quantity, this.QuantityTickSize)
    member this.RoundPrice          (price : decimal)   = Math.Round (price, this.PriceTickSize)

    // price deviation allowed from instrument price
    member this.LowAllowedPriceDeviation (basePrice: decimal)  = this.MinPriceMultiplier * basePrice
    member this.HighAllowedPriceDeviation (basePrice: decimal) = this.MaxPriceMultiplier * basePrice


module Instrument =
    let private  allInstruments   = Dictionary<string, Instrument>()
    let list     ()               = allInstruments.Values
    let register (instrument)     = allInstruments.[instrument.Ticker] <- instrument
    let exists   (ticker: string) = allInstruments.ContainsKey (ticker.ToUpper())
    let find     (ticker: string) = allInstruments.[ticker.ToUpper()]

在这个例子中,有一个带有数据的Instrument对象和一些帮助成员,还有一个模块,当需要按名称查找对象时充当存储库(在本例中为交易代码,因此它们是已知的和格式化的,它不是一个随机字符串)

我可以将帮助成员移动到模块,例如:

member this.LowAllowedPriceDeviation (basePrice: decimal)  = this.MinPriceMultiplier * basePrice

可以变成:

let lowAllowedPriceDeviation basePrice instrument = instrument.MinPriceMultiplier * basePrice

所以对象会变得更简单,最终可以变成一个简单的存储类型,没有任何增强。

但我想知道实际好处是什么(让我们只考虑可读性、可维护性等)?

此外,我不知道如何将其重新构建为不是一个类,缺少模块中的“内部”类并通过它执行所有操作,但这只会改变它。

回答

您关于转向LowAllowedPriceDeviation模块的直觉是正确的:它可以成为一个将this参数移到最后的函数。这是一种公认​​的模式。

Instrument类型上的所有其他方法也是如此。并且这两个私有静态方法可以成为模块中的私有函数。完全相同的方法。

现在的问题“这怎么会被重新构造成不是一个类”让我困惑了一下,因为这实际上不是一个类。Instrument是一个记录,而不是一个类。你给它一些实例和静态方法的事实并没有使它成为一个类。

最后(虽然,从技术上讲,这部分是基于意见的),关于“实际好处是什么” - 答案是“可组合性”。函数可以以方法不能的方式组合。

例如,假设您想要一种打印多种乐器的方法:

let printAll toString = List.iter (printfn "%s" << toString)

看看它是如何用toString函数参数化的?那是因为我想以不同的方式将它用于打印仪器。例如,我可能会打印他们的价格:

printAll priceToString (list())

但如果PriceToString是一种方法,我必须引入一个匿名函数:

printAll (fun i -> i.PriceToString) (list())

这看起来比使用函数要复杂一些,但实际上它很快就会变得非常复杂。然而,更大的问题是这甚至无法编译,因为类型推断对属性不起作用(因为它不能)。为了让它编译,你必须添加一个类型注释,使它更难看:

printAll (fun (i: Instrument) -> i.PriceToString) (list())

这只是函数可组合性的一个例子,还有很多其他例子。但我宁愿不写一篇关于这个主题的完整博客文章,它已经比我想要的要长得多。


以上是F#中的类型与模块的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>