Even the best type system will only get you so far, though sometimes a change in perspective and representation can get you a bit further...

Maybe change a "valid period" to either a "quarter" or a year, where each are unsigned int. This still admits a zero representation, which you don't want, but now the month-divisible-by-three problem is gone, along with the negative numbers. You can always add a member function "AsMonth" or whatever which multiplies by 3 or 12 to turn back into values you may use in another part of the computation.

In the end, the types will help give the program structure and rule out some large classes of potential bugs at compile-time, but then for the remainder of the potential bugs there are exceptions and run-time checks, which your tests will help expose.

By on 10/31/2008 9:36 PM ()

Thanks Brian and Julien for your responses. My questions were just trying to find out the limits of what can/cant be done from the F# type declarations and your answers have clarified it nicely thanks. I can see that my requirements would've made it nigh on impossible for the compiler to be able to statically check bindings in the code for correctness.

With regards to using types in this fashion:

type ValidPeriod =
| Month of int
| Year of int

If I then store ValidPeriod in vectors and matrices is there a difference from using pure ints/floats as the elements? i.e. is there a lot of extra checks/overheads that the compiler inserts for this?

By on 11/1/2008 5:28 AM ()

Discriminated unions are compiled into a class hierarchy; in this example ValidPeriod is an abstract base class and Month and Year are subclasses with int data members. (Try running reflector on the generated code.) So this is a non-trivial amount of overhead compared to arrays of integers in a high-performance application. You might find that defining a single struct type (ValidPeriod) with an int member, and some 'constructor functions' and possibly Active Patterns may be a better way to keep the allocation overhead down.

By on 11/1/2008 9:24 AM ()

If all your functions related to the ValidPeriod type are located in the same module (i.e. if you don't need access to the union structure outside), you can use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 

type ValidPeriod =    
    | Month of int 
    | Year of int 

let Month n = 
    if n % 3 = 0 && n > 0 then ValidPeriod.Month n else invalid_arg ("Month " + string n)
  
let Year n = 
    if n > 0 then ValidPeriod.Year n else invalid_arg ("Year " + string n)
  
//make sure pattern-matching works
let f =  function
    | Month n -> "ok"
    | Year n -> "ok again"
  
f (Month 9) 

And, in the signature file :

1
2
3
4
type ValidPeriod
val Month : n -> ValidPeriod
val Year : n -> ValidPeriod
val f : ValidPeriod -> unit

Far from perfect indeed...

By on 11/1/2008 4:39 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper