【问题标题】:Types vs. Modules in F#F# 中的类型与模块
【发布时间】:2021-03-31 17:29:14
【问题描述】:

Confused about static dictionary in a type, in 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

所以对象会变得更简单,最终可以变成简单的存储类型而无需任何扩充。

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

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

【问题讨论】:

  • 不需要“和”关键字。在不需要时使用该关键字是不好的做法,因为您向读者暗示存在递归依赖,而实际上没有。这只会让读者不必要地超载。

标签: f#


【解决方案1】:

您关于将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())

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-13
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    • 2016-08-23
    • 1970-01-01
    相关资源
    最近更新 更多