【问题标题】:F# Static Member Type ConstraintsF# 静态成员类型约束
【发布时间】:2010-05-15 15:51:13
【问题描述】:

我正在尝试定义一个函数 factorize,它使用类似于 Seq.sum 的结构类型约束(需要静态成员零、一、+ 和 /),以便它可以与 int、long、bigint 一起使用,等等。我似乎无法正确使用语法,也找不到很多关于该主题的资源。这是我的,请帮忙。

let inline factorize (n:^NUM) =
    ^NUM : (static member get_Zero: unit->(^NUM))
    ^NUM : (static member get_One: unit->(^NUM))
    let rec factorize (n:^NUM) (j:^NUM) (flist: ^NUM list) = 
        if n = ^NUM.One then flist
        elif n % j = ^NUM.Zero then factorize (n/j) (^NUM.One + ^NUM.One) (j::flist)
        else factorize n (j + ^NUM.One) (flist)
    factorize n (^NUM.One + ^NUM.One) []

【问题讨论】:

    标签: generics f# functional-programming constraints


    【解决方案1】:

    我是这样写的:

    module NumericLiteralG = begin
      let inline FromZero() = LanguagePrimitives.GenericZero
      let inline FromOne() = LanguagePrimitives.GenericOne
    end
    
    let inline factorize n = 
      let rec factorize n j flist =  
        if n = 1G then flist 
        elif n % j = 0G then factorize (n/j) j (j::flist) 
        else factorize n (j + 1G) (flist) 
      factorize n (1G + 1G) [] 
    

    此处为 factorize 推断的类型过于笼统,但该函数将按您的预期工作。如果您愿意,可以通过向一些泛型表达式添加显式类型来强制使用更健全的签名和一组约束:

    let inline factorize (n:^a) : ^a list = 
      let (one : ^a) = 1G
      let (zero : ^a) = 0G
      let rec factorize n (j:^a) flist =  
        if n = one then flist 
        elif n % j = zero then factorize (n/j) j (j::flist) 
        else factorize n (j + one) (flist) 
      factorize n (one + one) []
    

    【讨论】:

    • 你是对的 - 这很有效 - 对我来说真是一个惊喜:-)。我不确定这里发生了什么,因为factorize 被编译为通用函数。它使用GetZero 的动态实现(这可能类似于使用NumericAssociations),但我不确定这是如何工作的(没有为您自己的类型显式注册操作)。如果您了解这是如何工作的,我会对细节非常感兴趣:-)。
    • 刚刚注意到您在 elif 案例中为算法本身添加的优化。
    • @Tomas - 生成的通用函数是一个红鲱鱼 - 如果你真的调用它,你几乎肯定会在运行时得到一个 NotSupportedException,我不确定编译器为什么会发出它。相反,无论在哪里调用factorize,编译器都会使用参数类型的正确操作来内联整个定义,包括内部递归函数。这有帮助吗?
    • @kvb:啊,我明白了,诀窍是factorize 是尾递归(!),这意味着它被转换为循环并且实际上可以内联。这就是让我困惑的地方!
    • @Tomas - 我认为它是否是尾递归并不重要(例如,尝试在因式分解的两个递归调用之后添加一个@[])。如果你写let l = factorize 60,编译器基本上会生成类似let l = (fun n -> let rec factorize ... in factorize n ...) 60 的东西。您不能内联递归函数,但可以内联包含内部递归函数的函数,而不会遇到任何概念障碍。
    【解决方案2】:

    受到 kvb 使用 NumericLiterals 的回答的启发,我被驱使开发一种方法,该方法允许我们强制“合理”类型签名,而无需添加大量类型注释。

    首先我们为语言原语定义一些辅助函数和包装器类型:

    let inline zero_of (target:'a) : 'a = LanguagePrimitives.GenericZero<'a>
    let inline one_of (target:'a) : 'a = LanguagePrimitives.GenericOne<'a>
    let inline two_of (target:'a) : 'a = one_of(target) + one_of(target)
    let inline three_of (target:'a) : 'a = two_of(target) + one_of(target)
    let inline negone_of (target:'a) : 'a = zero_of(target) - one_of(target)
    
    let inline any_of (target:'a) (x:int) : 'a =
        let one:'a = one_of target
        let zero:'a = zero_of target
        let xu = if x > 0 then 1 else -1
        let gu:'a = if x > 0 then one else zero-one
    
        let rec get i g = 
            if i = x then g
            else get (i+xu) (g+gu)
        get 0 zero 
    
    type G<'a> = {
        negone:'a
        zero:'a
        one:'a
        two:'a
        three:'a
        any: int -> 'a
    }    
    
    let inline G_of (target:'a) : (G<'a>) = {
        zero = zero_of target
        one = one_of target
        two = two_of target
        three = three_of target
        negone = negone_of target
        any = any_of target
    }
    

    那么我们有:

    let inline factorizeG n = 
        let g = G_of n
        let rec factorize n j flist =  
            if n = g.one then flist 
            elif n % j = g.zero then factorize (n/j) j (j::flist) 
            else factorize n (j + g.one) (flist) 
        factorize n g.two []
    

    [编辑:由于 F# 2.0 / .NET 2.0 的一个明显错误,下面的 factorizen、factorizeL 和 factorizeI 在发布模式下编译时运行速度明显慢于 factorizeG,但在其他情况下运行速度稍快预期 -- 见F# performance question: what is the compiler doing?]

    或者我们可以更进一步(灵感来自 Expert F#,第 110 页):

    let inline factorize (g:G<'a>) n =   //'
        let rec factorize n j flist =  
            if n = g.one then flist 
            elif n % j = g.zero then factorize (n/j) j (j::flist) 
            else factorize n (j + g.one) (flist) 
        factorize n g.two []
    
    //identical to our earlier factorizeG
    let inline factorizeG n = factorize (G_of n) n
    
    let gn = G_of 1  //int32
    let gL = G_of 1L //int64
    let gI = G_of 1I //bigint
    
    //allow us to limit to only integral numeric types
    //and to reap performance gain by using pre-computed instances of G
    let factorizen = factorize gn
    let factorizeL = factorize gL
    let factorizeI = factorize gI
    

    此外,这里是 kvb 的 NumericLiteralG 的扩展版本,它允许我们使用“2G”、“-8G”等。虽然我不知道如何实现记忆策略(尽管这对 G 来说应该是可行的) .any)。

    module NumericLiteralG = 
        let inline FromZero() = LanguagePrimitives.GenericZero
        let inline FromOne() = LanguagePrimitives.GenericOne
        let inline FromInt32(n:int):'a =
            let one:'a = FromOne()
            let zero:'a = FromZero()
            let nu = if n > 0 then 1 else -1
            let gu:'a = if n > 0 then one else zero-one
    
            let rec get i g = 
                if i = n then g
                else get (i+nu) (g+gu)
            get 0 zero 
    

    【讨论】:

      【解决方案3】:

      首先,这是一个简单的例子,它显示了语法应该是什么样子:

      let inline zero< ^NUM when ^NUM : (static member get_Zero: unit-> ^NUM)> 
          (n:^NUM) = 
        (^NUM : (static member get_Zero : unit -> ^NUM) ())
      

      在某些情况下,您不需要显式编写约束(如果您编写上述内容,F# 编译器实际上会警告您),因为一些静态成员对编译器来说是众所周知的并且有标准函数使用它们。因此,您可以使用该函数,编译器将推断约束:

      let inline zero (n:^T) = 
        LanguagePrimitives.GenericZero< ^T > 
      

      不幸的是,这对你没有帮助,因为递归函数不能声明为inline(原因很明显——编译器不能在编译时内联函数,因为它不知道多少次),所以静态约束可能不足以解决您的问题。

      [EDIT:这实际上对于某些功能是可能的(见kvb的回答)]

      我认为您将需要 NumericAssociations,这已经在 in this question 讨论过(这些是在运行时处理的,因此它们速度较慢 - 但用于实现例如 F# 矩阵类型 - 矩阵可以动态缓存获得的信息,因此它是相当有效的)。

      【讨论】:

        猜你喜欢
        • 2020-12-20
        • 2017-12-18
        • 2018-02-26
        • 1970-01-01
        • 2019-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多