The short answer is yes: you have to write a separate function for each primitive numeric data type you want to support.

You can use the name of a numeric type as a function to convert to this numeric type. E.g., if you wanted to pass an int to the int64 version of factorial.

1
2
3
val factoriali64 : int64 -> int64

factoriali64 (int64 5)

F# supports some overloading in certain contexts. As zakaluka points out it supports OO-style overloading with the OverloadID attribute, but this only works on methods of classes not functions (afaik). Another form of overloading is with inline functions -- this is how operator overloading (e.g. +) is implemented in F#.

To write a truly generic factorial function, you could write something like this

1
2
3
4
5
6
7
8
9
10
11
12
open Math
open GlobalAssociations

let rec factorial (n : INumeric<_>) a =
  if a = n.Zero || a = n.One 
  then n.One 
  else n.Multiply(a, factorial n (n.Subtract(a, n.One)))

let i32 = factorial (GetNumericAssociation ()) 5
let i64 = factorial (GetNumericAssociation ()) 5L
let f = factorial (GetNumericAssociation ()) 5.0
let b = factorial (GetNumericAssociation ()) 5I

To this factorial function you need to pass an INumeric<'a> object which contains methods for addition, multiplication, etc. on the type 'a. To get a default implemenation for this interface for primitive numeric types you call the GetNumericAssociation function. However you can write your own implemenations of this interface for your own custom types and use it with this factorial function too.

By on 2/22/2008 10:16 PM ()

Actually you're real close to a fully generic solution here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#light 
open Math
open GlobalAssociations

let inline factorial (n : 'a) = 
    let a : INumeric<'a> = GetNumericAssociation()
    let rec factorial n = 
        if n = a.Zero || n = a.One then a.One
        else n * factorial (n - a.One)
    factorial n
    
factorial 10 |> printf "%A\n"
factorial 10L |> printf "%A\n"
factorial 10.0 |> printf "%A\n"
factorial 10.0f |> printf "%A\n"

 

val inline factorial :
   ^a ->  ^a
    when  ^a : (static member ( * ) :  ^a *  ^a ->  ^a) and
          ^a : (static member ( - ) :  ^a *  ^a ->  ^a)
3628800
3628800L
3628800.0
3628800.0f

Cheers,

Andy

By on 2/24/2008 2:57 AM ()

Welcome to the forums. You are correct, in that F# has great similarities to OCaml (and can still cross-compile F# code into OCaml bytecode).

Unlike OCaml, F# does support overloading in certain situations. For example, the same operator is used for addition for all the different types (+), as opposed to OCaml where you have (+) for integers and (+.) for floating point numbers.

The question you asked here is quite tricky. For one thing, F# does not perform automatic casting from int to int64 and vice versa (or between any two types of numbers, for that matter). Also, numbers are written differently for different types (1, 1., 1L, 1I, 1N, 1y, 1uy, ...). These different representations of the same constant cannot be compared directly, but must be explicitly type-cast first.

So, for your problem, you would have to do some extra work. Here is one way you could do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#light

type Factorial =
  | Vali of int
  | Valf of float
  
  static member ( * ) (x: Factorial, y: Factorial) = 
    match x, y with
    | Vali(a), Vali(b) -> Vali(a * b)
    | Valf(a), Valf(b) -> Valf(a * b)
    | _ -> failwith "shouldn't happen"

let helper a =
  match box a with
  | :? int as x -> Vali(x)
  | :? float as x -> Valf (x)
  | _ -> failwith "type not supported"

let fact a =
  let rec f' b =
    match b with
    | Vali(x) -> if x = 0 then Vali(1) else b * (f' (helper (x - 1)))
    | Valf(x) -> if x = 0. then Valf(1.) else b * (x - 1. |> helper |> f')
  f' <| helper a

As you can see, I basically created a new Factorial type that supports the multiplication operator (*). If you wanted to add another type, say, bigint, you would have to add it to the type definition, the (*) operator, the helper function and the internal f' function. However, if you look at the signature of fact, you see that it is val fact : 'a -> Factorial. This means that it will take a value of any type (since fact sends the argument to helper, which immediately boxes it). Here are some sample outputs:

1
2
3
4
5
6
7
8
9
> fact 5;;
val it : Factorial = Vali 120
> fact 5.;;
val it : Factorial = Valf 120.0
> fact 5L;;
Microsoft.FSharp.Core.FailureException: type not supported
   at FSI_0006.helper[T](T a)
   at <StartupCode$FSI_0012>.FSI_0012._main()
stopped due to error

A more reasonable solution may be to use the BigInt type.

1
2
3
4
5
6
7
8
9
10
11
12
13
open Microsoft.FSharp.Math

let fact2 a =
  let helper x =
    match box x with
    | :? int as y -> BigInt.of_int y
    | :? int64 as y -> BigInt.of_int64 y
    | :? string as y -> BigInt.of_string y
    | _ -> failwith "unsupported type"
  let rec f' b =
    if b = 0I then 1I
    else b * (f' (b - 1I))
  helper a |> f'

The code is much shorter. Here is some sample output:

1
2
3
4
5
6
7
8
9
10
> fact2 5;;
val it : bigint = 120I
> fact2 5L;;
val it : bigint = 120I
> fact2 5.;;
Microsoft.FSharp.Core.FailureException: unsupported type
   at <A href="mailto:FSI_0014.helper@1114@1114[U](U">FSI_0014.helper@1114@1114[U](U</A> x)
   at FSI_0014.fact2[T](T a)
   at <StartupCode$FSI_0017>.FSI_0017._main()
stopped due to error

Hope this helps,

z.

By on 2/22/2008 4:53 AM ()

helper a |> f'

That line is the only part I don't understand. Does it call helper and force it to act as the argument to the f' function? That is, the |> says to input the result of helper a into f', or am I just confusing myself? I can't find anything on this |> combination, so I'm wondering what its purpose is. Is it a separate operator from | and > or does it combine them? <:^)>

Edit: I found my answer in a PDF document that the Wikipedia entry on F# linked to.

By on 2/22/2008 8:26 AM ()

I figured things out a bit - like the fact that it is nearly impossible to do anything without the experimental lightweight feature...

Here is my final code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#light

open Microsoft.FSharp.Math

(* the actual factorial function *)
let fac n =

  (* 'x' is just a variable that holds the BigInt value of 'n' *)
  let x =
    match box n with
    | :? int as y -> BigInt.of_int y
    | :? int64 as y -> BigInt.of_int64 y
    | :? string as y -> BigInt.of_string y
    | _ -> failwith "Unsupported"

  (* the recursive function that does the work *)
  let rec f a =
    if (a = 0I or a = 1I) then 1I else a * (f (a - 1I))

  (* calling the recursive function *)
  f x

(*
  the main program
  (I can't stand the I at the end so I convert the BigInt to a string)
*)
printf "%s" (BigInt.to_string (fac 20))

This was really mind-boggling when I tried to figure it out without using #light. I had no clue how to use the 'in' statements to make it happy, though I figured out the semicolon parts at least. :P

Like someone said in another post I read earlier, the lightweight dialect will probably become the default. Without it, it is just too difficult in my opinion.

By on 2/22/2008 11:08 AM ()

That looks about right. However, note that the solution I showed above is quite verbose and only really useful when dealing with numbers of different types.

For a more generic solution, see the use of the OverloadID attribute. You can see some more information about it at:

[link:cs.hubfs.net]
[link:cs.hubfs.net]

There's also a little bit about it on Page 136 of Expert F# (Don Syme's book).

Regards,

z.

By on 2/22/2008 4:41 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper