注:我添加了很多Of interest
评论在最后。这些并不意味着建议人们应该使用inline
and static type parameters
无论如何,它们就在那里,这样人们就不必花费数小时搜索与此问题相关的大量 SO 问题来更好地理解这些概念。
我知道,当需要使函数通用并需要零 (0) 值时,F# 提供了通用零.
解析为任何原始数值类型或任何类型的零值
带有一个名为 Zero 的静态成员。
所以这让我相信使用GenericZero
对于字符串类型,我只需添加一个名为 Zero 的静态成员。
Since 系统字符串是.Net框架的一部分,修改.Net源代码不是应该做的事情。然而 F# 提供了类型扩展.
类型扩展允许您向先前定义的对象添加新成员
类型。
F# 还提供了字符串模块,但是缺少 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
结果出现错误:
类型“string”不支持运算符“get_Zero”
我确实检查了F# spec但没有找到任何帮助。
我可以添加 GenericZero 来输入吗System.String
,我在代码中做错了什么,还是我错过了文档中的某些内容?
TL;DR
搜索时感兴趣的问题
F# - 如何使用 get_Zero 扩展类型,以便可以通用地使用现有类型?
IMO 是一个误导性的标题,应该在阅读答案后更改。
F# 的 int 中的 get_Zero 是什么/在哪里?
杰克有一个很好的评论:
如果你从以下角度思考的话,零在字符串上没有意义
数字;但是,字符串上有一个零成员确实有意义
它返回空字符串,因为这会使字符串成为幺半群
在字符串连接下。
感兴趣的 F# 文档
自动泛化
F# 编译器在对函数执行类型推断时,
确定给定参数是否可以是通用的。编译器
检查每个参数并确定该函数是否具有
依赖于该参数的特定类型。如果没有,则
类型被推断为通用的。
类型推断
类型推断的想法是您不必指定
F# 构造的类型,除非编译器无法最终确定
推导类型。
对于那些你没有显式指定的类型,编译器
根据上下文推断类型。如果类型不是其他类型
指定,则推断它是通用的。
Generics
F# 函数值、方法、属性和聚合类型,例如
类、记录和可区分联合可以是通用的。通用的
构造体至少包含一个类型参数,通常是
由通用构造的用户提供。通用函数和
类型使您能够编写适用于各种类型的代码
无需为每种类型重复代码。使您的代码具有通用性
在 F# 中要简单,因为您的代码通常会被隐式推断为
通过编译器的类型推断和自动泛化实现泛型
机制。
静态解析类型参数
静态解析的类型参数是这样的类型参数
在编译时而不是在运行时替换为实际类型。
它们前面有插入符号 (^)。
静态解析的类型参数是主要用于
与成员约束相结合,这是允许的约束
您指定类型参数必须具有特定成员或
会员才能使用。没有办法创建这种类型
使用常规泛型类型参数进行约束。
在 F# 语言中,有两种不同类型的参数。
第一种是标准泛型类型参数。这些都是
用撇号 (') 表示,如 'T 和 'U。它们是等价的
到其他 .NET Framework 语言中的泛型类型参数。这
另一种是静态解析的,并由插入符号指示,
如 ^T 和 ^U 所示。
约束条件
内联函数
当您使用静态类型参数时,任何函数
由类型参数化的参数必须是内联的.
EDIT
这是一个使用的示例GenericZero
对于用户定义的类型,无需使用有效的扩展,并且有两个变体表明GenericZero
不适用于intrinsic extension
and optional extension
先运行程序看看GenericZero
工作然后取消注释行Program.fs
查看错误intrinsic extension
and optional extension
.
An 内在延伸是出现在相同的扩展名
命名空间或模块,位于同一源文件和同一程序集中
(DLL 或可执行文件)作为被扩展的类型。
An 可选扩展是出现在外部的扩展名
正在扩展的类型的原始模块、命名空间或程序集。
当类型被检查时,内在扩展出现在类型上
反射,但可选扩展则不然。可选扩展必须
位于模块中,并且仅当模块
包含扩展名已打开。
In 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")
in 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")
in 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