views:

520

answers:

3

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 with int, long, bigint, etc. I can't seem to get the syntax right, and can't find a lot of resources on the subject. This is what I have, please help.

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) []
+2  A: 

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).

Tomas Petricek
+10  A: 

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) []
kvb
Wow, that's awesome.
Stephen Swensen
You're right - this works - quite a surprise for me :-). I'm not sure what's going on here, because `factorize` is compiled as a generic function. It uses dynamic implementation of `GetZero` (which is probably similar to using `NumericAssociations`), but I'm not sure how does this work (without explicit registration of the operations for your own type). If you understand how this works, I would be quite interested in the details :-).
Tomas Petricek
Just noticed the nice optimization you added in the elif case to the algorithm itself.
Stephen Swensen
@Tomas - the generated generic function is a red herring - you'll almost certainly get a NotSupportedException at runtime if you actually call it, and I'm not sure why the compiler emits it at all. Instead, wherever `factorize` is called, the compiler inlines the whole definition, including the inner recursive function, using the correct operations for the argument type. Does that help?
kvb
@kvb: Aaah, I see, the trick is that `factorize` is tail-recursive (!), which means that it is translated to a loop and can be actually inlined. That's what was confusing me!
Tomas Petricek
@Tomas - I don't think it matters whether or not it is tail recursive (e.g. try adding an `@[]` after both recursive calls in factorize). If you write `let l = factorize 60`, the compiler essentially generates something like `let l = (fun n -> let rec factorize ... in factorize n ...) 60`. You can't inline a recursive function, but you can inline a function which contains an inner recursive function without any conceptual hurdles.
kvb
@kvb: Thanks, it finally makes sense to me :-)!
Tomas Petricek
+2  A: 

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 http://stackoverflow.com/questions/2945880/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 
Stephen Swensen