如何将LanguagePrimitives.GenericZero/get_Zero加到System.String?
How to add LanguagePrimitives.GenericZero / get_Zero to System.String?
注:我在最后加了很多Of interest
条评论。这些并不意味着建议应该使用 inline
和 static type parameters
willy nilly,它们在那里是为了让人们不必花费数小时搜索与此问题相关的大量 SO 问题以更好地理解这些概念。
我知道当需要使函数通用并且需要零 (0) 值时,F# 提供 GenericZero.
Resolves to the zero value for any primitive numeric type or any type
with a static member called Zero.
所以这让我相信要将 GenericZero
与字符串类型一起使用,我只需添加一个名为 Zero 的静态成员。
自 System.String is part of the .Net framework, modifying the .Net source code is not what should be done. However F# provides Type Extensions.
Type extensions let you add new members to a previously defined object
type.
F# 还提供了 String module,但缺少 GenericZero。
有关创建类型扩展的优秀教程参考:Attaching functions to types。
我测试的代码:
这是在一个名为 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
导致错误:
The type 'string' does not support the operator 'get_Zero'
我检查了 F# spec 但没有找到任何帮助。
我可以添加 GenericZero 来键入 System.String
,我是在代码中做错了什么,还是在文档中遗漏了什么?
TL;DR
SO搜索时感兴趣的问题
哪个 IMO 是一个误导性的标题,应该在阅读答案后更改。
What/where is get_Zero in F#'s int?
杰克的评论很好:
Zero doesn't make sense on string if you think of it in terms of
numerics; however, it does make sense to have a Zero member on string
which returns the empty string, as that would make string a monoid
under string concatenation.
感兴趣的 F# 文档
The F# compiler, when it performs type inference on a function,
determines whether a given parameter can be generic. The compiler
examines each parameter and determines whether the function has a
dependency on the specific type of that parameter. If it does not, the
type is inferred to be generic.
The idea of type inference is that you do not have to specify the
types of F# constructs except when the compiler cannot conclusively
deduce the type.
For those types that you do not specify explicitly, the compiler
infers the type based on the context. If the type is not otherwise
specified, it is inferred to be generic.
F# function values, methods, properties, and aggregate types such as
classes, records, and discriminated unions can be generic. Generic
constructs contain at least one type parameter, which is usually
supplied by the user of the generic construct. Generic functions and
types enable you to write code that works with a variety of types
without repeating the code for each type. Making your code generic can
be simple in F#, because often your code is implicitly inferred to be
generic by the compiler's type inference and automatic generalization
mechanisms.
Statically Resolved Type Parameters
A statically resolved type parameter is a type parameter that is
replaced with an actual type at compile time instead of at run time.
They are preceded by a caret (^) symbol.
Statically resolved type parameters are primarily useful in
conjunction with member constraints, which are constraints that allow
you to specify that a type argument must have a particular member or
members in order to be used. There is no way to create this kind of
constraint by using a regular generic type parameter.
In the F# language, there are two distinct kinds of type parameters.
The first kind is the standard generic type parameter. These are
indicated by an apostrophe ('), as in 'T and 'U. They are equivalent
to generic type parameters in other .NET Framework languages. The
other kind is statically resolved and is indicated by a caret symbol,
as in ^T and ^U.
When you use static type parameters, any functions that are
parameterized by type parameters must be inline.
编辑
这是一个示例,它使用 GenericZero
作为用户定义的类型,但没有使用有效的扩展名,并且两个变体表明 GenericZero
不适用于 intrinsic extension
和 optional extension
运行 程序首先看到 GenericZero
工作,然后取消注释 Program.fs
中的行以查看 intrinsic extension
和 optional extension
的错误。
An intrinsic extension is an extension that appears in the same
namespace or module, in the same source file, and in the same assembly
(DLL or executable file) as the type being extended.
An optional extension is an extension that appears outside the
original module, namespace, or assembly of the type being extended.
Intrinsic extensions appear on the type when the type is examined by
reflection, but optional extensions do not. Optional extensions must
be in modules, and they are only in scope when the module that
contains the extension is open.
在 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
扩展成员不被视为成员约束解决方案的一部分,所以你运气不好。对于涉及不止一种类型的约束(例如 (+)
上的约束),您可以通过使用第二种类型来解决此问题,但是对于 GenericZero
上的约束没有好的解决方法。
注:我在最后加了很多Of interest
条评论。这些并不意味着建议应该使用 inline
和 static type parameters
willy nilly,它们在那里是为了让人们不必花费数小时搜索与此问题相关的大量 SO 问题以更好地理解这些概念。
我知道当需要使函数通用并且需要零 (0) 值时,F# 提供 GenericZero.
Resolves to the zero value for any primitive numeric type or any type with a static member called Zero.
所以这让我相信要将 GenericZero
与字符串类型一起使用,我只需添加一个名为 Zero 的静态成员。
自 System.String is part of the .Net framework, modifying the .Net source code is not what should be done. However F# provides Type Extensions.
Type extensions let you add new members to a previously defined object type.
F# 还提供了 String module,但缺少 GenericZero。
有关创建类型扩展的优秀教程参考:Attaching functions to types。
我测试的代码:
这是在一个名为 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
导致错误:
The type 'string' does not support the operator 'get_Zero'
我检查了 F# spec 但没有找到任何帮助。
我可以添加 GenericZero 来键入 System.String
,我是在代码中做错了什么,还是在文档中遗漏了什么?
TL;DR
SO搜索时感兴趣的问题
哪个 IMO 是一个误导性的标题,应该在阅读答案后更改。
What/where is get_Zero in F#'s int?
杰克的评论很好:
Zero doesn't make sense on string if you think of it in terms of numerics; however, it does make sense to have a Zero member on string which returns the empty string, as that would make string a monoid under string concatenation.
感兴趣的 F# 文档
The F# compiler, when it performs type inference on a function, determines whether a given parameter can be generic. The compiler examines each parameter and determines whether the function has a dependency on the specific type of that parameter. If it does not, the type is inferred to be generic.
The idea of type inference is that you do not have to specify the types of F# constructs except when the compiler cannot conclusively deduce the type.
For those types that you do not specify explicitly, the compiler infers the type based on the context. If the type is not otherwise specified, it is inferred to be generic.
F# function values, methods, properties, and aggregate types such as classes, records, and discriminated unions can be generic. Generic constructs contain at least one type parameter, which is usually supplied by the user of the generic construct. Generic functions and types enable you to write code that works with a variety of types without repeating the code for each type. Making your code generic can be simple in F#, because often your code is implicitly inferred to be generic by the compiler's type inference and automatic generalization mechanisms.
Statically Resolved Type Parameters
A statically resolved type parameter is a type parameter that is replaced with an actual type at compile time instead of at run time. They are preceded by a caret (^) symbol.
Statically resolved type parameters are primarily useful in conjunction with member constraints, which are constraints that allow you to specify that a type argument must have a particular member or members in order to be used. There is no way to create this kind of constraint by using a regular generic type parameter.
In the F# language, there are two distinct kinds of type parameters. The first kind is the standard generic type parameter. These are indicated by an apostrophe ('), as in 'T and 'U. They are equivalent to generic type parameters in other .NET Framework languages. The other kind is statically resolved and is indicated by a caret symbol, as in ^T and ^U.
When you use static type parameters, any functions that are parameterized by type parameters must be inline.
编辑
这是一个示例,它使用 GenericZero
作为用户定义的类型,但没有使用有效的扩展名,并且两个变体表明 GenericZero
不适用于 intrinsic extension
和 optional extension
运行 程序首先看到 GenericZero
工作,然后取消注释 Program.fs
中的行以查看 intrinsic extension
和 optional extension
的错误。
An intrinsic extension is an extension that appears in the same namespace or module, in the same source file, and in the same assembly (DLL or executable file) as the type being extended.
An optional extension is an extension that appears outside the original module, namespace, or assembly of the type being extended. Intrinsic extensions appear on the type when the type is examined by reflection, but optional extensions do not. Optional extensions must be in modules, and they are only in scope when the module that contains the extension is open.
在 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
扩展成员不被视为成员约束解决方案的一部分,所以你运气不好。对于涉及不止一种类型的约束(例如 (+)
上的约束),您可以通过使用第二种类型来解决此问题,但是对于 GenericZero
上的约束没有好的解决方法。