sdgfsdh
sdgfsdh

Reputation: 37045

Generic values in F# modules?

Consider a generic container like:

type Foo<'t> = 
  {
    Foo : 't
  }

This generic function works OK:

module Foo = 
  
  let inline emptyFoo () = 
    {
      Foo = LanguagePrimitives.GenericZero
    }

But this value does not:

module Foo = 
  
  let emptyFoo = 
    {
      Foo = LanguagePrimitives.GenericZero
    }

This is because the compiler infers emptyFoo to have type Foo<obj>.

However, the standard library has generic values like List.empty, so how is that achieved there?

Upvotes: 4

Views: 101

Answers (2)

David Raab
David Raab

Reputation: 4488

You could implement a List by yourself, with an empty. It looks like this.

type L<'a> =
    | Null
    | Cons of 'a * L<'a>

module L =
    let empty = Null

let xs = Cons(1,  Cons(2,  L.empty))
let ys = Cons(1.0,Cons(2.0,L.empty))

So, why does L.empty in this case works in a generic way? Because the value Null has no special value attached to it. You could say, it is compatible with every other generic.

In your Record on the other hand, you always must produce a value. LanguagePrimitive.GenericZero is not some Generic value. It help so to resolve to a special zero value, and this value is determined by the other code you write.

For example

let x = LanguagePrimitives.GenericZero

is also obj

let x = LanguagePrimitives.GenericZero + 1

will be int. And

let x = LanguagePrimitives.GenericZero + 1.0

will be float. So in some case you can think of GenericZero just as a placeholder for zero for the special type you need, but the code needs to determine at this point, which type you want.

You could change your type, with an option to provide a real empty.

type Foo<'t> = {
    Foo: 't option
}

module Foo =
    let empty = { Foo = None }

let x = Foo.empty                  // Foo<'a>
let y = { x with Foo = Some 1   }  // Foo<int>
let z = { x with Foo = Some 1.0 }  // Foo<float>

Zero Member

Maybe you want a Zero Member on some types. For example

type Vector3 = {X:float; Y:float; Z:float} with
    static member create x y z = {X=x; Y=y; Z=z}
    static member (+) (a,b)    = Vector3.create (a.X + b.X) (a.Y + b.Y) (a.Z + b.Z)
    static member Zero         = Vector3.create 0.0 0.0 0.0
    static member DivideByInt(a,b) =
        Vector3.create
            (LanguagePrimitives.DivideByInt a.X b)
            (LanguagePrimitives.DivideByInt a.Y b)
            (LanguagePrimitives.DivideByInt a.Z b)

then you can write

let xs = [1;2;3]
let ys = [Vector3.create 1.0 1.0 1.0; Vector3.create 1.0 1.0 1.0]

let inline sum xs =
    List.fold (fun a b -> a + b) LanguagePrimitives.GenericZero xs

let sumx = sum xs // int:     6
let sumy = sum ys // Vector3: 2.0 2.0 2.0

Upvotes: 1

You have to make it explicitly generic.

List.empty is implemented like this:

let empty<'T> = ([ ] : 'T list)

Upvotes: 1

Related Questions