Enter a line that is not empty or spaces in F # to represent

I like the simplicity of types like

type Code = Code of string 

But I would like to impose some restrictions on the line (in this case, do not allow empty lines only for spaces). Sort of

 type nonemptystring = ??? type Code = Code of nonemptystring 

How to define this type in F # on an idiomatic path? I know that I can create a class with a constructor or a restricted module with a factory function, but is there an easy way?

+6
source share
2 answers

A string is essentially a sequence of char values ​​(in Haskell, BTW, string is a type alias for [Char] ). Then there will be a more general question if it is possible to statically declare a list as having a given size.

Such a language function is known as Dependent Types , but F # does not have it. Therefore, the short answer is that this cannot be done in a declarative way.

The simplest and probably the most idiomatic way is to define Code as a single discriminatory union:

 type Code = Code of string 

In the module that defines Code , you also define a function that clients can use to create Code values:

 let tryCreateCode candidate = if System.String.IsNullOrWhiteSpace candidate then None else Some (Code candidate) 

This function contains runtime logic that prevents clients from creating empty Code values:

 > tryCreateCode "foo";; val it : Code option = Some (Code "foo") > tryCreateCode "";; val it : Code option = None > tryCreateCode " ";; val it : Code option = None 

What prevents the client from creating an invalid Code value, eh? For example, could the client bypass the tryCreateCode function and simply write Code "" ?

Here are the signature files . You create a signature file ( .fsi ) and in this declare types and functions like this

 type Code val tryCreateCode : string -> Code option 

The Code type is declared here, but its "constructor" is not. This means that you cannot directly create values ​​of these types. This, for example, does not compile:

 Code "" 

Indicated error:

error FS0039: value, constructor, namespace, or Code type not defined

The only way to create a Code value is to use the tryCreateCode function.

As stated here, you can no longer access the base string value of Code , unless you also provide a function for this:

 let toString (Code x) = x 

and declare it in the same .fsi file as above:

 val toString : Code -> string 

This may seem like a lot of work, but in reality it’s just six lines of code and three lines of type declaration (in a .fsi file).

+3
source

Unfortunately, there is no convenient syntax for declaring a limited subset of types, but I would use active templates for this. As you say correctly, you can make a type and check its validity when creating it:

 /// String type which can't be null or whitespace type FullString (string) = let string = match (System.String.IsNullOrWhiteSpace string) with |true -> invalidArg "string" "string cannot be null or whitespace" |false -> string member this.String = string 

Now, building this type naively can throw runtime exceptions, and we don't want that! So let's use active templates:

 let (|FullStr|WhitespaceStr|NullStr|) (str : string) = match str with |null -> NullStr |str when System.String.IsNullOrWhiteSpace str -> WhitespaceStr |str -> FullStr(FullString(str)) 

Now we have something we can use with the pattern matching syntax to build our FullString s. This function is safe at runtime because we only create FullString if we are in an acceptable case.

You can use it as follows:

 let printString str = match str with |NullStr -> printfn "The string is null" |WhitespaceStr -> printfn "The string is whitespace" |FullStr fstr -> printfn "The string is %s" (fstr.String) 
0
source

All Articles