Appendix A. Troubleshooting

This appendix details many of the problems commonly encountered by new F# programmers.

Value Restriction

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.

MUTABLE ARRAY CONTENTS

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.

NEGATIVE LITERALS

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.

ACCIDENTAL CAPTURE

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.

LOCAL AND NON-LOCAL VARIABLE DEFINITIONS

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#.

MERGING LINES

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.

APPLICATIONS THAT DO NOT DIE

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.

BEWARE OF "IT"

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.215.96