I'm following this

let StringOfLengthConstructor<'c> (input:string, length:int, defaultConstructor:string->'c) =
    match input with
    | null -> None
    | x when x.Length = length -> Some(defaultConstructor(input))
    | _ -> None
type String3 = | String3 of string
let String3 input = StringOfLengthConstructor<String3>(input, 3, String3)    
type String4 = | String4 of string
let String4 input= StringOfLengthConstructor<String4>(input, 4, String4)

type String5 = | String5 of string
let String5 input = StringOfLengthConstructor<String5>(input, 5, String5)
type String6 = | String6 of string
let String6 input = StringOfLengthConstructor<String6>(input,6, String6)

and am trying to make more of these with hopefully less repetition. Can this be done without having to repeat type String3 =| String3 of string followed by this?

let String3 input = StringOfLengthConstructor<String3>(input, 3, String3)  

See all the repetition? For the purpose of defining a zip code, a US zip code could be defined as

type ZipCode = | Us of String5


type ZipCode = | Us of String5*(String4 option) | Canadian of String3*String3
  • 3
    \$\begingroup\$ Could you explain what are you trying to do and why? \$\endgroup\$ – svick Jun 19 '14 at 17:22
  • 1
    \$\begingroup\$ To make life easier for reviewers, please add sufficient context to your question. The more you tell us about what your code does and what the purpose of doing that is, the easier it will be for reviewers to help you. See also this meta question \$\endgroup\$ – Simon Forsberg Jun 19 '14 at 18:00
  • \$\begingroup\$ the difference is that the type String3 = | String 3 of string together with their smart constructor (the let String 3 looks to me like a quer try to make some) will be type-checked - and as it is you have to repeat those definitions for the compiler (I don't think we will ever get dependent-types in F#/.net ;) ) \$\endgroup\$ – Carsten Jul 4 '14 at 8:38

The solution can be made cleaner by re-arranging the order of the arguments and using pointfree style.

let StringOfLengthConstructor<'c> (length : int) (defaultConstructor : string -> 'c) (input : string) =
    match input with
    | x when x <> null && x.Length = length -> Some (defaultConstructor input)
    | _ -> None

type String3 = String3 of string
let String3 = StringOfLengthConstructor 3 String3

However, I don't think that String5 * (String4 option) is a good definition of a zip code. The code should be validating on more than just the string length.

  • \$\begingroup\$ for sure I don't think this is great validation for a zip code, it's more just the stubs of making sure I can do what I want to do at a higher level first \$\endgroup\$ – Maslow Jun 24 '14 at 13:05

Concerning the smart constructor thing I mentioned - I would implement the types like this:

module SafeLengthStrings =

   let private assertLength (length : int) (input : string) : string =
       if input.Length <> length 
       then failwith (sprintf "expected string of length %d but got %s" length input)
       else input

   type String3 = private Str3 of string
   let string3 : (string -> String3) =
       assertLength 3 >> Str3

This way you cannot construct malformed String3 instances at runtime (minus Reflection of course)

  • \$\begingroup\$ yeah I see the difference, and the leaking abstraction of doing it the other way. I'm trying to walk down the exception-less ideals of some articles I've been reading lately and see where it takes me. \$\endgroup\$ – Maslow Jul 4 '14 at 20:57
  • \$\begingroup\$ yeah why not - in this case I would think about getting the maybe-Monad in there \$\endgroup\$ – Carsten Jul 5 '14 at 6:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.