Low overhead type definitions
https://swlaschin.gitbooks.io/fsharpforfunandprofit/content/posts/conciseness-type-definitions.html
在C#中,编程者们创建新类型的积极性很低:缺乏类型推理意味着你需要在大多数地方明确地指定类型,这导致了脆性和更多的视觉混乱。因此,人们总是倾向于创建单体类而不是将其模块化。
在 F# 中,创建新类型几乎没有开销,因此存在数百甚至数千个新类型是很常见的。每次需要定义结构时,都可以创建一个特殊类型,而不是重用(和重载)现有类型,例如字符串和列表。
这意味着你的程序将更具有类型安全性,更具有自我记录性【自我解释,自成文档】,更具有可维护性(因为当类型发生变化时,你将立即得到编译时的错误而不是运行时的错误)。
以下是 F# 中单行代码类型的一些示例:
open System
// some "record" types
type Person = {FirstName:string; LastName:string; Dob:DateTime}
type Coord = {Lat:float; Long:float}
// some "union" (choice) types
type TimePeriod = Hour | Day | Week | Year
type Temperature = C of int | F of int
type Appointment = OneTime of DateTime
| Recurring of DateTime listF# 类型和域驱动设计
F# 中类型系统的简洁性在进行域驱动设计 (DDD) 时特别有用。在 DDD 中,对于每个现实世界的实体和值对象,理想情况下您都希望有一个对应的类型。这可能意味着创建数百个“小”类型,这在 C# 中可能很乏味。
此外,DDD 中的“值”对象应该具有结构相等性,这意味着包含相同数据的两个对象应该始终相等。在 C# 中,这可能意味着重写 IEquatable 时更加乏味,但在 F# 中,默认情况下您可以无开销的获得它。【不过现在C# 也有 Record 类型了,自动在IEquatable上做加工,相当于自动生成了】
为了展示在 F# 中创建 DDD 类型有多么容易,这里有一些示例类型可以为简单的“客户”域创建。
type PersonalName = {FirstName:string; LastName:string}
// Addresses
type StreetAddress = {Line1:string; Line2:string; Line3:string }
type ZipCode = ZipCode of string
type StateAbbrev = StateAbbrev of string
type ZipAndState = {State:StateAbbrev; Zip:ZipCode }
type USAddress = {Street:StreetAddress; Region:ZipAndState}
type UKPostCode = PostCode of string
type UKAddress = {Street:StreetAddress; Region:UKPostCode}
type InternationalAddress = {
Street:StreetAddress; Region:string; CountryName:string}
// choice type -- must be one of these three specific types
type Address = USAddress | UKAddress | InternationalAddress
// Email
type Email = Email of string
// Phone
type CountryPrefix = Prefix of int
type Phone = {CountryPrefix:CountryPrefix; LocalNumber:string}
type Contact =
{
PersonalName: PersonalName;
// "option" means it might be missing
Address: Address option;
Email: Email option;
Phone: Phone option;
}
// Put it all together into a CustomerAccount type
type CustomerAccountId = AccountId of string
type CustomerType = Prospect | Active | Inactive
// override equality and deny comparison
[<CustomEquality; NoComparison>]
type CustomerAccount =
{
CustomerAccountId: CustomerAccountId;
CustomerType: CustomerType;
ContactInfo: Contact;
}
override this.Equals(other) =
match other with
| :? CustomerAccount as otherCust ->
(this.CustomerAccountId = otherCust.CustomerAccountId)
| _ -> false
override this.GetHashCode() = hash this.CustomerAccountId此代码片段在短短几行中包含 17 个类型定义,但复杂度极低。做同样的事情需要多少行 C# 代码?
显然,这是一个只有基本类型的简化版本?在实际系统中,会添加约束和其他方法。但是请注意,创建大量 DDD 值对象是多么容易,尤其是字符串的包装类型,例如“ZipCode”和“Email”。通过使用这些包装器类型,我们可以在创建时强制执行某些约束,还可以确保这些类型不会与普通代码中的无约束字符串混淆。唯一的“实体”类型是 CustomerAccount,它被清楚地指示为具有平等和比较的特殊待遇。
有关更深入的讨论,请参阅名为“F# 中的域驱动设计”的系列。【todo:添加链接】
Last updated