如何将LanguagePrimitives.GenericZero/get_Zero加到System.String?

How to add LanguagePrimitives.GenericZero / get_Zero to System.String?

注:我在最后加了很多Of interest条评论。这些并不意味着建议应该使用 inlinestatic 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# 文档

Automatic Generalization

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.

Type Inference

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.

Generics

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.

Constraints

Inline Functions

When you use static type parameters, any functions that are parameterized by type parameters must be inline.

编辑

这是一个示例,它使用 GenericZero 作为用户定义的类型,但没有使用有效的扩展名,并且两个变体表明 GenericZero 不适用于 intrinsic extensionoptional extension

运行 程序首先看到 GenericZero 工作,然后取消注释 Program.fs 中的行以查看 intrinsic extensionoptional 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 上的约束没有好的解决方法。