Can I set a "is a Discriminated Union" type constraint on a generic argument in F# type declaration?

228 Views Asked by At

I am creating a type system for some generic types of common input:

// type Validator<'t> when 't :> ??? = Validator of 't
// with member x.validate value condition =
//         if condition then
//             (true, Some ('t value))
//         else
//             (false, None)

module Validatable =
    let validate t value condition =        
        if condition then
            (true, Some (t value))
        else
            (false, None)

type Email = Email of string
with member x.validate(s) = Validatable.validate Email s (Regex.IsMatch(s, ""))

type Name = Name of string
with member x.validate(s) = Validatable.validate Name s (Regex.IsMatch(s, ""))

type PhoneNumber = PhoneNumber of string
with member x.validate(s) = Validatable.validate PhoneNumber s (Regex.IsMatch(s, ""))

You'll see that within the comment, I have another type commented out. I am hoping to use the type defined within the comment to replace the functionality of the validate t value condition function within the Validatable module.

What do I need to replace the ??? with to allow me to say that the generic parameter 't is the case identifier of a Discriminated Union?

2

There are 2 best solutions below

2
On BEST ANSWER

A union case isn't a type, it's a function that produces a value of type. So you can write your Validator type like this instead:

type Validator<'inp, 'out> = Validator of ('inp -> 'out)
with member x.validate value condition =
        let (Validator f) = x
        if condition then
            (true, Some (f value))
        else
            (false, None)

And use it like this:

type Email = Email of string
with member x.validate(s) = (Validator Email).validate s (Regex.IsMatch(s, ""))

type Name = Name of string
with member x.validate(s) = (Validator Name).validate s (Regex.IsMatch(s, ""))

type PhoneNumber = PhoneNumber of string
with member x.validate(s) = (Validator PhoneNumber).validate s (Regex.IsMatch(s, ""))
0
On

It is really hard to tell what are you actually trying to do. But if you want to write some function that works on multiple types of values, you generally can use interfaces and do not need generic type constraints.

For example, if you wanted to have a generic way of validating certain kinds of values using regular expressions, you could define an IValidable interface:

type IValidable = 
  abstract Value : string
  abstract ValidationRegex : string

Then implement the interface in all the types you want to validate:

type Email = 
  | Email of string
  interface IValidable with
    member x.Value = let (Email v) = x in v
    member x.ValidationRegex = "[a-z]+@[a-z]+.[a-z]+"
  
type PhoneNumber = 
  | PhoneNumber of string
  interface IValidable with
    member x.Value = let (PhoneNumber v) = x in v
    member x.ValidationRegex = "[0-9]+"

And now you can write a simple validate function and use it with all your values:

let validate (v:IValidable) =
  System.Text.RegularExpressions.Regex.IsMatch(v.Value, v.ValidationRegex)
  
validate (Email "no")
validate (Email "[email protected]")
validate (PhoneNumber "no")
validate (PhoneNumber "123")