Michal Pawluk
Michal Pawluk

Reputation: 581

How to access static members of a type declared as generic parameter in F#

I would like to create a base type in F# which would allow me to simplify creation of other types with predefined constraint ie. limit to non negative integer etc.. I creted the following base type which intends to use static member of a validator type provided as a generic parameter hoping I will be able to refer to its static "Condition" member - unfortunately it seems I cannot refer to the generic ^tf anywhere in the type body, not to mention its static member.

  type Constrained<^tf when ^tf:(static member Condition: 'tv->bool)> (value : int) =
     static member Create (v) =             
        match ^tf.Condition v with
        | true -> Some ( Constrained(v) )
        | _ -> None

I'm new to F# so I'm not sure if what I'm trying to do can be actually achieved in F#...

Upvotes: 3

Views: 1187

Answers (3)

Tomas Petricek
Tomas Petricek

Reputation: 243096

You can solve this using static member constraints and the other answers show how to do that. However, I wonder if that gives you any practical benefit over treating the conditions as ordinary functions. For example, when you use your type, you'll have to write something like:

Constrained<Conditions.NonNegative>.Create 42

If you passed the condition as an ordinary function, you could write:

Constrained.Create Conditions.nonNegative 42

There is nothing wrong with static member constraints, but they are a fairly advanced language feature and I'm a big fan of keeping things simple, so I'd only use them if they are really needed. (And using functions lets you use things like partial applications if you wish.)

For completeness, the function based version would be:

type Constrained(value : int) =
    static member Create condition v =             
        match condition v with
        | true -> Some ( Constrained(v) )
        | _ -> None

module Conditions =
  let positive n = n > 0

Constrained.Create Conditions.positive 42

Upvotes: 3

Gus
Gus

Reputation: 26204

After reading again your question, I think you want to get working some code like this:

type T = T with
    static member Condition (x) = x > 0

type Constrained< ^tf when ^tf:(static member Condition: int->bool)> (value : int) =
    static member inline Create (v) =             
        match (^tf : (static member Condition : int->bool) v) with
        | true -> Some ( Constrained(v) )
        | _    -> None


let x:Constrained<T> option = Constrained<T>.Create(1)

But not sure what is this buying you.

Upvotes: 4

TheQuickBrownFox
TheQuickBrownFox

Reputation: 10624

Here's a way to do it with a DU instead of a class. The advantage of this is that you get default structural equality.

module Constrained =
    type Constrained< ^a when ^a : (member Condition : bool) > =
        private Constrained of ^a with
        static member inline Create (x:^a ) =
            if (^a : (member Condition : bool) x) then Some (Constrained x)
            else None
        member inline this.Value : ^a = match this with Constrained v -> v

type PosInt = PosInt of int with member this.Condition = this |> fun (PosInt x) -> x > 0
type NonEmptyString = NonEmptyString of string with member this.Condition = this |> fun (NonEmptyString x) -> x.Length > 0

open Constrained

Constrained.Create (PosInt -1) // None
Constrained.Create (NonEmptyString "") // None

(Constrained.Create (PosInt 1)) // Some (Constrained (PosInt 1))
(Constrained.Create (NonEmptyString "a")) // Some (Constrained (NonEmptyString "a"))

let f (x:Constrained<PosInt>) =
    match x.Value with PosInt i -> i + 1

Constrained (PosInt -1) // Compile error. The private union case constructor cannot be used outside of the Constrained module

You can use a class if you use the same constraint. Note that you can't write Foo<^a ...> in F#. The syntax is only valid with a space like this: Foo< ^a ...>

Upvotes: 2

Related Questions