This appendix details many of the problems commonly encountered by new F# programmers.
One problem encountered when using an F# interactive session is the value restriction, which requires types to be completely defined.
The simplest example impeded by the value restriction is the definition of a reference to an empty list:
> let x = ref [];; ----^^stdin(2,4): error: FS0030: Value restriction. Type inference has inferred the signature val x : '_a list ref Either define 'x' as a simple data term, make it a
function, or add a type constraint to instantiate the type parameters.
The empty list has the well-defined type 'a list
but a reference to the empty list does not have a well-defined type because the empty list might be replaced by a non-empty list, in which case the elements must be of a specific type, the type of which has not been defined. In this case, the value restriction protects the programmer from accidentally setting the reference to an int list
in one part of the program but a float list
in another part of the program.
This problem can always be addressed by adding a type annotation to constrain the type of the value. In this case, we might know that the list will contain int
values, so we annotate accordingly to make the F# interactive session happy:
> let x : int list ref = ref [];; val x : int list ref
The problem occurs mainly when executing parts of a program being developed in an F# interactive session.
If an array is initialized from a reference value using the Array. create
function then the reference is shared between every element of the array. For example, the following creates an array containing 3 elements, all of which are the same reference to an integer:
> let a = Array.create 3 (ref 0);; val a : int ref array
Assigning the reference from any element causes all elements to be assigned:
> a. [0] := 7;; > a;; val it : int ref array = [|{contents = 7}; {contents = 7}; {contents = 7}|]
This problem is most easily solved by using the Array. init
function to create the array, specifying a function that returns a new reference at each invocation:
> let a = Array.init 3 (fun _ -> ref 0);; val a : int ref array
or using a comprehension:
> let a = [|for i in 0 . . 2 -> ref 0|];;
Every array element now contains its own mutable value:
> a.[0] := 7; a;;
val it : int ref array = [|{contents = 7}; {contents = 0}; {contents = 0}|]
This caveat applies to any instantiation of an array with mutable elements, including arrays of hash tables.
As a special case, floating point literals that are arguments do not need to be bracketed. For example, f
(-1.0) (-2 . 0) may be written f
−1.0 −2.0.
Although very useful, this syntax is currently somewhat fragile. In particular, the positions of spaces are important. For example, the following is interpreted incorrectly as a function application rather than a subtraction:
> 1.0-2.0;; stdin(3,0): error: FS0003: This is not a function and cannot be applied. Did you forget a'
;'
or'
in'
?
F# understood that to mean 1. 0 (- 2 . 0)
, i.e. applying - 2 . 0
as an argument. This example must therefore be spaced out:
> 1.0 - 2.0;; val it : float = −1.0
Consequently, it is good style to add spaces between operators when possible, in order to disambiguate them.
Closures are local function definitions that can capture local variables. Captured variables are stored inside the closure by the F# compiler and are referred to as the environment of the closure. This is a powerful form of automated abstraction and is one of the cornerstones of functional programming.
This form of abstraction has an important caveat that F# programmers should be aware of. The environment of a closure can accidentally capture important variables such as file handles and keep them alive for longer than expected. This should not be a problem for well written programs as external resources such as file handles should always be closed explicitly via the use
construct but programmers often succumb to the temptation of abusing the garbage collector for the release of external resources. This can cause leaks if closures capture references that keep the resources alive.
Although similar in appearance, the let
keyword is used in two different ways and this can be invasive when using the traditional syntax. Specifically, non-nested (outermost):
let ... = ...in
constructs make new definitions in the current namespace whereas nested:
let ... = ... in
constructs make local definitions.
The difference between nested and non-nested definitions can sometimes be confusing. For example, the following is valid F# code (written in traditional syntax for clarity) that defines a variable a:
> let a = let b = 4 in b * b;; val a : int = 16
In contrast, the following tries to make a non-local definition for a within the nested expression for b, which is invalid:
> let b = 4 in let a = b * b;; Syntax error
This is one of the trivial sytactic mistakes often made when learning F#.
The #light
syntax option circumvents some of the problems with the traditional syntax but also introduces new problems. For example, the following is intended to be read as a pair of separate lines:
let links = getWords >> List.filter ((=) "href") google |> links
Attempting to evaluate these two lines at once (by selecting them both and pressing ALT+ENTER
) gives an error because the compiler actually interprets the code as:
let links = getWords >> List.filter ((=) "href") google |> links
This ambiguity can be resolved in various ways but the simplest solution is to use the;; token to delimit the blocks of code.
Multithreaded GUI applications sometimes suffer from the problem that a worker thread continues executing unnecessarily after all user-visible functionality has disappeared when an application is closed. This can be addressed by marking the worker thread as a background thread using the IsBackground
property of the Thread
object.
Evaluation of an expression rather than a definition in an interactive session yields a response of the form:
val it : ...
As you may have guessed, this actually defines a variable it
to have the value resulting from the evaluation, i.e. it
may be used to refer to the last result.
This can be very useful. However, it will overwrite any variable called it
that you have defined and it
is a tempting variable name to use when referring to an iterator.
18.188.37.136