Hi,
If you want to provide additional arguments to the container that would provide basic functionality for some operations performed by the container (like sum of all elements, maximal element, etc..) you can also use interfaces and write something like this:

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
// Defines standard operations that the type should support
type INumeric<'a> =
  abstract Zero : 'a
  abstract One  : 'a
  abstract Add  : 'a -> 'a -> 'a
  abstract Mul  : 'a -> 'a -> 'a

// similarly add implementation for other basic types that you want to support...
let floatNum = 
  { new INumeric<float> with
      member x.Zero = 0.0
      member x.One = 1.0
      member x.Add a b = a + b
      member x.Mul a b = a * b }

// The container expects the implementation of numerics as a second argument
type MyContainer<'a> (data:'a list, nums:INumeric<'a>) =
  member x.Sum = data |> List.fold_left nums.Add nums.Zero
  member x.Mul = data |> List.fold_left nums.Mul nums.One

// Some example use...
let cont = new MyContainer<float>([1.0; 2.0; 3.0; 4.0], floatNum)
let sum = cont.Sum

Actually, you can use a global associations provided by F# - it is based on the technique that I just demonstrated with the exception that you can dynamically get an implementation of an interface similar to INumeric from the F# runtime. It can be used like this:

1
2
3
4
5
6
7
8
9
open Microsoft.FSharp.Math.GlobalAssociations;;

let sumList (data:'a list) =
  // get the F# 'INumeric' interface implementation for a type 'a
  let num = GetNumericAssociation<'a>()
  data |> List.fold_left (fun a b -> num.Add(a,b)) num.Zero;;

// sumList [1; 2; 3; 4; 5] ... returns 15
// sumList [1.1; 2.2; 3.3; 4.4; 5.5] ... returns 16.5

Though, as far as I know there were some discussions in the F# team about introducing a more elegant soluition to this problem, so maybe there will be some better solution in the future versions. I think this is quite common problem.

By on 10/22/2007 5:16 PM ()

Tomas and Z,

Thanks for your quick responses. I guess I should have made it a little clearer what I meant by a "cleaner way" to write this.

I am aware that this is a problem with generics and C# (at least). But when I saw the library

1
Microsoft.FSharp.Core.Operators 

and specifically functions like (abs: 'a -> 'a) or (cos: 'a -> 'a) (these are numeric functions after all) or even compare: 'a -> 'a -> 'a, I thought that maybe there is a (+) operator that works on generic types which would resolve the issue at compile time. (I could not find the library Microsoft.FSharp.Core.Operators.Checked, though the manual says it is there).

I guess the GetNumericAssociation<'a>() comes pretty close to what I was looking for.

Thanks again

Regards

Chris

By on 10/22/2007 7:24 PM ()

There is an operator (+) which will resolve it at compiler time, however, the (+) and cos, sin, abs, etc... All work using code expansion which means they aren't truly generic functions but that a new copy is created for each type that you use it with (similar to C++ templates in that respect), to create you're own functions like this you need to mark them as inline, for example:

1
let inline sum c = c.vec |> Seq.fold(fun sum x -> sum + x) c.zeroVal

I wasn't able to get it working as a member since then the type variable of the container would need to be constrained to have a (+) member then which I couldn't figure out the syntax for (assuming it can be done)

By on 10/23/2007 6:36 PM ()

By cleaner, do you mean the interface you present to other developers or in the organization of the code?

If you are only referring to the interface, the easy way would be to use an interface file. Hide the record type for container, create a 'static member Create' or something similar that will take 3 parameters and return a fully constructed 'container'. In addition, add another field to the record that will hold your addition function. Then, change sum to take () instead of plusFn. This will change your calls to (for example):

1
2
let c = container.Create 0.0F [1.0F; 2.0F; 3.0F] (+)
c.sum ()

If you are referring to the organization of the code, the better route would be to use interfaces and classes. Change 'container' into an interface or abstract class. Then, create a number of classes that implement that interface, such as IntContainer, FloatContainer, MatrixContainer, etc. Finally, when you instantiate one of these child classes, pass in the correct parameters and immediately cast it to its parent class. The operations will be virtual and look "nicer".

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type 'a IContainer = class
  abstract sum : unit -> 'a 
  abstract mult : unit -> 'a
end

type FloatContainer(_zero, _vec, _add, _mul) = class
  let zeroVal = _zero

  let vec = _vec
  let plusFn = _add
  let multFn = _mul
  inherit IContainer with
    ...
let x = ((new FloatContainer(0.0F, [1.0F; 2.0F], (+), (*))) :> IContainer<float>)

I'm not sure the syntax is absolutely right for the classes, but that's the general idea.

A third option is to create a combination record/module, with the module containing all static methods (take all function members away from the record), similar to the Map/List/Seq modules. This is much the same as the first idea, but the implementation takes away all dot notation (so, Container.sum cont vs cont.sum ()). The organization is also a little clearer.

The last idea would be to try and use reflection to dynamically call functions. It would make the code more convoluted on the inside, but on the outside it would appear very clean. This is very similar to idea 2 as all the classes need to follow a standard interface in order for such calls to work.

Regards,

Z.

By on 10/22/2007 4:43 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