Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type to represent a string which is not empty or spaces in F#

Tags:

f#

I love the simplicity of types like

type Code = Code of string

But I would like to put some restrictions on string (in this case - do not allow empty of spaces-only strings). Something like

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

How do I define this type in F# idiomatic way? I know I can make it a class with constructor or a restricted module with factory function, but is there an easy way?

like image 716
Mikhail Shilkov Avatar asked Dec 10 '15 22:12

Mikhail Shilkov


1 Answers

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

Such a language feature is know as Dependent Types, and F# doesn't have it. The short answer, therefore, is that this is not possible to do in a declarative fashion.

The easiest, and probably also most idiomatic, way, then, would be to define Code as a single-case Discriminated Union:

type Code = Code of string

In the module that defines Code, you'd 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 the run-time 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 a client from creating an invalid Code value, then? For example, wouldn't a client be able to circumvent the tryCreateCode function and simply write Code ""?

This is where signature files come in. You create a signature file (.fsi), and in that declare types and functions like this:

type Code
val tryCreateCode : string -> Code option

Here, the Code type is declared, but its 'constructor' isn't. This means that you can't directly create values of this types. This, for example, doesn't compile:

Code ""

The error given is:

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

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

As given here, you can no longer access the underlying string value of Code, unless you also provide a function for that:

let toString (Code x) = x

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

val toString : Code -> string

That may look like a lot of work, but is really only six lines of code, and three lines of type declaration (in the .fsi file).

like image 146
Mark Seemann Avatar answered Oct 28 '22 07:10

Mark Seemann