9

注意:我在最后添加了很多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 extensionoptional 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
4

1 回答 1

5

扩展成员不被视为成员约束解决方案的一部分,因此您不走运。对于涉及一种以上类型的约束(例如 on 的约束(+)),您可以通过使用第二种类型来解决此问题,但对于 on 的约束GenericZero没有好的解决方法。

于 2016-05-09T13:52:08.663 回答