fsl
fsl

Reputation: 831

F# type definition with expression

Is it possible to express something like this:

type id = int > 0

I know its not possible to do statically, since this would mean F# has dependent types. In C# I'm used to do this sort of thing with code contracts and get a runtime enforcement. I'm looking for something similiar here.

Thanks

EDIT: Thank you for all the answers which have various pros and cons. At the monent I'm only using a small subset of F#, a subset of the ocaml core that lends itself easily to program proofs. So no classes.

Upvotes: 8

Views: 998

Answers (4)

J D
J D

Reputation: 48687

Define it as a union type:

type Id = Id of int

and shadow the constructor with another function:

let Id n =
  assert(n > 0)
  Id n

Upvotes: 4

Be Brave Be Like Ukraine
Be Brave Be Like Ukraine

Reputation: 7735

Contrary to what others said, I would suggest not using classes here, if I understood your problem correctly.
Since the value is immutable, we need applying constraint only once. Any wrapper classes would be an overhead and load GC. Instead, a simple function will do the job:

let inline constrained predicate errormessage value =
    if not (predicate value)
    then invalidArg "value" errormessage
    else value

let positive =
    constrained (fun x -> x > 0) "Value must be positive"

let int1 = positive 5      // OK
let int2 = positive -3     // ArgumentException

You can do the same for other types:

let mustBeLong =
    constrained (fun (x:string) -> x.Length > 3) "String must be long"

let str1 = mustBeLong "foobar"  // OK
let str2 = mustBeLong "baz"     // ArgumentException

Using the same within a struct:

type Point2D =
   struct 
      val X: int
      val Y: int
      new(x: int, y: int) = { X = positive x; Y = positive y }
   end

let point1 = Point2D(5, 3)  // OK
let point2 = Point2D(5, -2) // ArgumentException

Upvotes: 8

pad
pad

Reputation: 41290

In F#, you have to resort to classes and check arguments inside constructors. Other types such as discriminated unions, records and structs have implicit constructors which you can't easily alter.

type Id(i: int) =
    do if i <= 0 then 
         invalidArg "i" "the argument has to be a positive integer"
    member x.Value = i

Pattern matching doesn't play nicely with classes. You can remedy the problem using active patterns:

let (|Id|) (id: Id) = id.Value

let id = Id(1)

match id with
| Id 1 -> printfn "matched"
| _ -> printfn "unmatched"

Upvotes: 2

John Palmer
John Palmer

Reputation: 25516

You could create a generic class like so:

type verify<'t>(t:'t,cond) =
    let mutable tval = t
    let _verify v = if not (cond v) then failwith "bad argument"
    do _verify tval
    member x.get() = tval
    member x.set v =
        _verify v 
        tval <- v

then you can use it with

verify(1,fun t -> t>0)

using .set will recheck the condition.

Upvotes: 1

Related Questions