注意:我在最后添加了很多Of interest
评论。这些并不意味着建议人们应该随意使用它们inline
,它们的存在是为了让人们不必花费数小时搜索与该问题相关的大量 SO 问题来更好地理解这些概念。static type parameters
我知道当需要使函数通用并需要零 (0) 值时,F# 提供GenericZero。
解析为任何原始数值类型或任何具有称为 Zero 的静态成员的类型的零值。
所以这让我相信要使用GenericZero
字符串类型,我只需要添加一个名为 Zero 的静态成员。
由于System.String是 .Net 框架的一部分,因此不应该修改 .Net 源代码。但是 F# 提供类型扩展。
类型扩展允许您将新成员添加到先前定义的对象类型。
F# 还提供了String 模块,但缺少 GenericZero。
有关创建类型扩展的优秀教程,请参考:将函数附加到类型。
我测试的代码:
这是在一个名为Library1
namespace Extension.Test
module Extensions =
type System.String with
static member Something = "a"
static member StaticProp
with get() = "b"
static member Zero
with get() = "c"
这是在一个名为Workspace
namespace Extension.Test
module main =
open Extensions
[<EntryPoint>]
let main argv =
let stringSomething = System.String.Something
printfn "something: %s" stringSomething
let staticProperty = System.String.StaticProp
printfn "staticProperty: %s" staticProperty
let zeroProperty = System.String.Zero
printfn "zeroProperty: %s" zeroProperty
let inline addTest (x : ^a) (y : ^a) : ^a =
x + y
let intAdd = addTest 2 LanguagePrimitives.GenericZero
let floatAdd = addTest 2.0 LanguagePrimitives.GenericZero
// let stringAdd = addTest "a" LanguagePrimitives.GenericZero
printfn "intAdd: %A" intAdd
printfn "floatAdd: %A" floatAdd
// printfn "stringAdd: %A" stringAdd
printf "Press any key to exit: "
System.Console.ReadKey() |> ignore
printfn ""
0 // return an integer exit code
运行时输出:
something: a
staticProperty: b
zeroProperty: c
intAdd: 2
floatAdd: 2.0
Press any key to exit
所以我正在创建和访问扩展成员并使用 GenericZero 没有任何问题。
最后一部分是使用 GenericZero 作为字符串,但是取消注释该行
let stringAdd = addTest "a" LanguagePrimitives.GenericZero
导致错误:
“字符串”类型不支持运算符“get_Zero”
我确实检查了F# 规范,但没有找到任何帮助。
我可以将 GenericZero 添加到 typeSystem.String
吗,我在代码中做错了什么,还是我错过了文档中的某些内容?
TL;博士
SO搜索时感兴趣的问题
F# - 如何使用 get_Zero 扩展类型,以便可以通用地使用现有类型?
IMO 是一个误导性的标题,应该在阅读答案后更改。
F# 的 int 中的 get_Zero 是什么/在哪里?
杰克有一个很好的评论:
如果您从数字的角度来考虑,零在字符串上没有意义;但是,在返回空字符串的字符串上有一个 Zero 成员确实有意义,因为这会使字符串在字符串连接下成为一个幺半群。
感兴趣的 F# 文档
F# 编译器在对函数执行类型推断时,会确定给定参数是否可以是泛型的。编译器检查每个参数并确定函数是否依赖于该参数的特定类型。如果不是,则类型被推断为泛型。
类型推断的想法是,您不必指定 F# 构造的类型,除非编译器无法最终推断出类型。
对于那些没有明确指定的类型,编译器会根据上下文推断类型。如果没有另外指定类型,则推断为泛型。
F# 函数值、方法、属性和聚合类型(例如类、记录和可区分联合)可以是通用的。泛型构造包含至少一个类型参数,通常由泛型构造的用户提供。泛型函数和类型使您能够编写适用于各种类型的代码,而无需为每种类型重复代码。在 F# 中使您的代码通用化可能很简单,因为编译器的类型推断和自动泛化机制通常会隐式推断您的代码是通用的。
静态解析类型参数是在编译时而不是在运行时替换为实际类型的类型参数。它们前面有一个插入符号 (^)。
静态解析的类型参数主要与成员约束结合使用,成员约束是允许您指定类型参数必须具有特定成员或成员才能使用的约束。没有办法通过使用常规泛型类型参数来创建这种约束。
在 F# 语言中,有两种不同的类型参数。第一种是标准的泛型类型参数。这些用撇号 (') 表示,如 'T 和 'U。它们等效于其他 .NET Framework 语言中的泛型类型参数。另一种是静态解析的,由插入符号表示,如 ^T 和 ^U。
当您使用静态类型参数时,任何由类型参数参数化的函数都必须是 inline。
编辑
这是一个示例,它GenericZero
用于用户定义的类型,而不使用有效的扩展名和两个显示GenericZero
不适用的变体intrinsic extension
和optional extension
首先运行程序以查看GenericZero
工作,然后取消注释中的行Program.fs
以查看 和 的intrinsic extension
错误optional extension
。
内在扩展是与被扩展的类型出现在同一命名空间或模块、同一源文件和同一程序集(DLL 或可执行文件)中的扩展。
可选扩展是出现在被扩展类型的原始模块、命名空间或程序集之外的扩展。当通过反射检查类型时,类型上会出现内在扩展,但可选扩展不会。可选扩展必须在模块中,并且仅当包含扩展的模块打开时它们才在范围内。
在Library1.fs
项目中Library1
namespace Extension.Test
module module001 =
// No extension
type MyType01(x: string) =
member this.x = x
override this.ToString() = this.x.ToString()
static member Something = MyType01("a")
static member (+) (mt1 : MyType01, mt2 : MyType01) = MyType01(mt1.x + mt2.x)
static member (+) (mt1 : MyType01, s : string) = MyType01(mt1.x + s)
static member (+) (s : string, mt2 : MyType01) = MyType01(s + mt2.x)
static member Zero
with get() = MyType01("b")
// uses intrinsic extension
type MyType02(x: string) =
member this.x = x
override this.ToString() = this.x.ToString()
static member Something = MyType02("g")
static member (+) (mt1 : MyType02, mt2 : MyType02) = MyType02(mt1.x + mt2.x)
static member (+) (mt1 : MyType02, s : string) = MyType02(mt1.x + s)
static member (+) (s : string, mt2 : MyType02) = MyType02(s + mt2.x)
// static member Zero
// with get() = MyType02("h")
// uses optional extension
type MyType03(x: string) =
member this.x = x
override this.ToString() = this.x.ToString()
static member Something = MyType03("m")
static member (+) (mt1 : MyType03, mt2 : MyType03) = MyType03(mt1.x + mt2.x)
static member (+) (mt1 : MyType03, s : string) = MyType03(mt1.x + s)
static member (+) (s : string, mt2 : MyType03) = MyType03(s + mt2.x)
// static member Zero
// with get() = MyType03("n")
module module002 =
open module001
// intrinsic extension
type MyType02 with
static member Zero
with get() = MyType02("h")
在Library2.fs
项目中Library2
namespace Extension.Test
open module001
module module003 =
type MyType01 with
static member Anything = MyType02("c")
type MyType02 with
static member Anything = MyType02("i")
// optional extension
type MyType03 with
static member Anything = MyType03("p")
static member Zero
with get() = MyType03("n")
在Program.fs
项目中Workspace
namespace Workspace
open Extension.Test.module001
open Extension.Test.module002
open Extension.Test.module003
module main =
[<EntryPoint>]
let main argv =
let staticFromBaseType = MyType01.Something
printfn "MyType01 staticFromBaseType: %A" staticFromBaseType
let staticFromExtensionType = MyType01.Anything
printfn "MyType01 staticFromExtensionType: %A" staticFromExtensionType
let zeroValue = MyType01.Zero
printfn "MyType01 zeroValue: %A" zeroValue
let (genericZero: MyType01) = LanguagePrimitives.GenericZero
printfn "MyType01 genericZero: %A" genericZero
let staticFromBaseType = MyType02.Something
printfn "MyType02 staticFromBaseType: %A" staticFromBaseType
let staticFromExtensionType = MyType02.Anything
printfn "MyType02 staticFromExtensionType: %A" staticFromExtensionType
let zeroValue = MyType02.Zero
printfn "MyType02 zeroValue: %A" zeroValue
// let (genericZero: MyType02) = LanguagePrimitives.GenericZero
// printfn "MyType02 genericZero: %A" genericZero
let staticFromBaseType = MyType03.Something
printfn "MyType03 staticFromBaseType: %A" staticFromBaseType
let staticFromExtensionType = MyType03.Anything
printfn "MyType03 staticFromExtensionType: %A" staticFromExtensionType
let zeroValue = MyType03.Zero
printfn "MyType03 zeroValue: %A" zeroValue
// let (genericZero: MyType03) = LanguagePrimitives.GenericZero
// printfn "MyType03 genericZero: %A" genericZero
let inline addTest (x : ^a) (y : ^a) : ^a =
x + y
let intAdd = addTest 2 LanguagePrimitives.GenericZero
let floatAdd = addTest 2.0 LanguagePrimitives.GenericZero
let (myType01Add : MyType01) = addTest (MyType01("d")) LanguagePrimitives.GenericZero
// let (myType02Add : MyType02) = addTest (MyType02("d")) LanguagePrimitives.GenericZero
// let (myType03Add : MyType03) = addTest (MyType03("o")) LanguagePrimitives.GenericZero
printfn "intAdd: %A" intAdd
printfn "floatAdd: %A" floatAdd
printfn "myType01Add: %A" myType01Add
// printfn "myType02Add: %A" myType02Add
// printfn "myType03Add: %A" myType03Add
printf "Press any key to exit: "
System.Console.ReadKey() |> ignore
printfn ""
0 // return an integer exit code