奇技淫巧:F#泛型特化的最终优化

       上回讲到,F#中 module 里的泛型变量实际上是泛型函数。由此,在一个泛型特化的实现中出了岔子。最后通过泛型得以解决。使用 F# 来做泛型特化,相比 C# 有这个优势:F# 中直接支持函数赋值,而无须使用委托。避免了 C# 例子里面,为了节省 Lumbda 与委托之间的转换,而做出的丑陋的反射、拆箱操作。代码不再这么艰涩,我们重新来看看这段代码:

        module Mithra =

        type Cache<'T>() =

            static member val GetBytes: 'T->byte[] = Unchecked.defaultof< 'T -> byte[] > with get, set 

        Cache<int16>.GetBytes <- BitConverter.GetBytes

        Cache<int32>.GetBytes <- BitConverter.GetBytes

        Cache<int64>.GetBytes <- BitConverter.GetBytes

        Cache<String>.GetBytes <- Encoding.UTF8.GetBytes 

        let GetBytes<'T> = Cache<'T>.GetBytes

 
  还要重申这点:F# 中 module 里的泛型变量实际上是泛型函数。所以,最后这句 let GetBytes<'T> = Cache<'T>.GetBytes     仍然是函数调用。我们使用 let a = GetBytes<String>("123")      时,实际上调用的代码却是:

        let GetBytes = GetBytes<String>()

        let a = GetBytes("123")

       实际上等于多了一层薄薄的函数封装 。但是,即使是 0.01mm,也是隔靴搔痒。
       要在 F# 中想去掉这层封装,却需要点技巧。我们的目的是可以进行这样的调用:
   let a = Cache<String>.GetBytes("123")

     目前不可如此,因为直接调用的话,就不会执行后面的特化语句,就会调出一个 null 函数。C# 的例子是把 Cache<T> 放在另一个静态类型里,而把特化代码放在该静态类型的静态构造中,第一次进入静态类就会调用构造函数,就会执行此特化代码。F# 不可以,它的语法不允许把一个类衔套到另一个类里面。类可以放在 module 里面,但是 module 不完全等于 C# 静态类。调用 module 里面的类不会执行 module 里面的赋值代码。所以只能用互相递归的类来模拟:  

    type Cache<'T>() =

        static member val GetBytes: 'T->byte[] = Unchecked.defaultof< 'T ->byte[] > with get, set

        // 必须为 val,才会执行后面的代码

        static member val private __ = cachEx.__

    andcachEx() =

        static do Cache<int16>.GetBytes <- BitConverter.GetBytes

        static do Cache<int32>.GetBytes <- BitConverter.GetBytes

        static do Cache<int64>.GetBytes <- BitConverter.GetBytes

        static do Cache<String>.GetBytes <- Encoding.UTF8.GetBytes

        static do Cache<slice>.GetBytes <- fun (src, idx, len)-> src.[idx..idx + len - 1]

        // 这里须为属性或者函数,才会执行 static do

        static member internal __ = ()

       当调用 Cache<String>.GetBytes 函数的时候,最先执行的是 Cache<String的静态函数,F# 中没有明确的静态函数,微软的文档里叫做静态绑定,就是执行所有的 static let 和 static do 语句,另外,文档里没有明确提及的,计算 static member val 的值。所以,现在是先计算 Cache<String>.GetBytes 函数的默认值,然后再计算 Cache<String>.__ 的值。后一项计算则调用了递归类的 cachEx().__  属性。 而调用这个属性之前必须先自动调用 cachEx() 的静态函数,就是特化工作,我们的目的达到了。cachEx() 的静态构造只会执行一次,也就意味着特化工作也不会重复执行。最后,为了使用方便,我们还可以:

    letinline GetBytes<'T> = Cache<'T>.GetBytes 

F# 中的 
inline 是 100% 内联,不像C++。C++ 是不确定的。有时,不确定的内联带来的不仅仅是性能的不确定,还有可能是行为上的不确定。正如一开始的那段代码。GetBytes 不内联的话,运行是正确的,但是内联了之后,就会出错。如果加了 inline 之后,编译器有时候选择内联,有时候不选择内联,估计编程者要郁闷死了。所幸的是,普通的C++没有泛型,只有C++/Cli有。

原文地址:https://www.cnblogs.com/greatim/p/3947632.html