F# 中的类型与模块
Types vs. Modules 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 只是不想放弃 classes(尽管当我们离开 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
所以对象会变得更简单,最终可以变成一个简单的存储类型,无需任何扩充。
但我想知道有什么实际好处(让我们只考虑可读性、可维护性等)?
此外,我看不出如何将其重新构造为不是 class,缺少模块中的 'internal' class 并执行所有操作通过那个,但这只会改变它。
您关于将 LowAllowedPriceDeviation
转为模块的直觉是正确的:它可以成为将 this
参数移至末尾的函数。这是一个公认的模式。
Instrument
类型的所有其他方法也是如此。这两个私有静态方法可以成为模块中的私有函数。完全相同的方法。
问题 “如何将其重新构造为不是 class” 让我有点困惑,因为这实际上不是 class. Instrument
是一条记录,不是 class。事实上,你给了它一些实例和静态方法并不能使它成为 class.
最后(尽管从技术上讲,这部分是基于意见的),关于 “实际好处是什么” - 答案是“可组合性”。函数可以用方法不能的方式组合。
例如,假设您想要一种打印多个仪器的方法:
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())
这只是函数可组合性的一个例子,还有很多其他例子。但是我不想就这个主题写一整篇博客 post,它已经比我想要的长很多了。
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 只是不想放弃 classes(尽管当我们离开 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
所以对象会变得更简单,最终可以变成一个简单的存储类型,无需任何扩充。
但我想知道有什么实际好处(让我们只考虑可读性、可维护性等)?
此外,我看不出如何将其重新构造为不是 class,缺少模块中的 'internal' class 并执行所有操作通过那个,但这只会改变它。
您关于将 LowAllowedPriceDeviation
转为模块的直觉是正确的:它可以成为将 this
参数移至末尾的函数。这是一个公认的模式。
Instrument
类型的所有其他方法也是如此。这两个私有静态方法可以成为模块中的私有函数。完全相同的方法。
问题 “如何将其重新构造为不是 class” 让我有点困惑,因为这实际上不是 class. Instrument
是一条记录,不是 class。事实上,你给了它一些实例和静态方法并不能使它成为 class.
最后(尽管从技术上讲,这部分是基于意见的),关于 “实际好处是什么” - 答案是“可组合性”。函数可以用方法不能的方式组合。
例如,假设您想要一种打印多个仪器的方法:
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())
这只是函数可组合性的一个例子,还有很多其他例子。但是我不想就这个主题写一整篇博客 post,它已经比我想要的长很多了。