Reputation: 581
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
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
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
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