F# Static Member Type Constraints

前端 未结 3 1143
伪装坚强ぢ
伪装坚强ぢ 2020-12-02 23:39

I\'m trying to define a function, factorize, which uses structural type constraints (requires static members Zero, One, +, and /) similar to Seq.sum so that it can be used w

相关标签:
3条回答
  • 2020-12-03 00:04

    Firstly, here is a trivial example that shows how the syntax should look like:

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

    In some cases, you don't need to write the constraints explicitly (the F# compiler will actually warn you about that if you write the above), because some static members are well-known to the compiler and there are standard functions for using them. So, you can use the function and the compiler will infer the constraint:

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

    Unfortunately, this really doesn't help you, because recursive functions cannot be declared as inline (for obvious reasons - the compiler cannot inline the function at compile time, because it doesn't know how many times), so static constraints are probably not powerful enough for your problem.

    [EDIT: This is actually possible for some functions (see kvb's answer)]

    I think you'll need NumericAssociations instead, which were alreaday discussed in this question (these are processed at runtime, so they are slower - but are used to implement for example F# matrix type - the matrix can cache the dynamically obtained information, so it is reasonably efficient).

    0 讨论(0)
  • 2020-12-03 00:08

    Here's how I'd write it:

    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) [] 
    

    The type inferred for factorize here is way too general, but the function will work as you'd expect. You can force a more sane signature and set of constraints if you want by adding explicit types to some of the generic expressions:

    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) []
    
    0 讨论(0)
  • 2020-12-03 00:23

    Inspired by kvb's answer using NumericLiterals, I was driven to develop an approach which would allow us to force "sane" type signatures without having to add extensive type annotations.

    First we define some helper functions and wrapper type for language primitives:

    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
    }
    

    Then we have:

    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 []
    

    [Edit: due to an apparent bug with F# 2.0 / .NET 2.0, factorizen, factorizeL, and factorizeI below run significantly slower than factorizeG when compiled in Release-mode but otherwise run slightly faster as expected -- see F# performance question: what is the compiler doing?]

    Or we can take it a few step further (inspired by Expert F#, p.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
    

    Also, here is an extended version of kvb's NumericLiteralG which allows us to use "2G", "-8G", etc. Though I couldn't figure out how to implement a memoization strategy (though that should be doable for 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 
    
    0 讨论(0)
提交回复
热议问题