|
F# Manual
- Contents
Quick Tour: Some Basics
This section takes a quick tour through some of the most important expressions and definitional forms used in F# code, in particular See also the tutorials on calling .NET libraries and using F# code from .NET libraries. Literal Constants are much as in other languages. See the literals section in the language specification for full details:
Lists are a commonly used data structure. They are not mutable, i.e., you can't delete an element of a list – instead you create a new list with the element deleted. List values often share storage under the hood, i.e., a list value only allocate more memory when you actually execute construction operations.
Tuples let you group several values into a single value.
Conditional Expressions evaluate one of two expressions depending on the value of a guard:
F# statements are simply expressions that return a special value of a type called "unit". Multiple expressions can be chained together using the sequencing ";" operator. For loops and while loops are much as in other languages.
F# functions and values are typically defined using let and/or let rec keywords.
Definitions may be local, i.e., you can define new functions and values within the expression that is the definition of another value. These definitions are similar to local definitions in other languages, except that you can both define functions locally and return these function values as part of the overall result of the expression. Local function definitions may "capture" variables. The use of local bindings is very common in F# code.
Recursion is a way of specifying a function or value whose definition depends on itself. The most common use of recursion is to define function values that walk over data structures. Functions can also be mutually recursive, as can many other values (see the Advanced section).
Types are at the heart of F# programming. Every value and expression is given a static type. Some examples of types are given in the table below (Note: syntactic types are essentially always prefixed with a colon :).
Essentially all .NET types are also types in F# code. For example System.Object is a type in F#, as its abbreviation obj, as is System.String and its abbreviation string. This means that a number of F# types that appear to be built-in to the F# language are actually simply equivalent to .NET types (e.g., type string = System.String, type int = System.Int32). Some of the standard predefined abbreviations for types in the .NET or F# library are shown below.
F# is statically typed. This means that all expressions are given a type through the static typing rules of the language. F# code is also dynamically typed in the sense that certain values permit the runtime discovery of type information through type tests, and dynamic types such as obj can represent essentially any F# value. Types whose values do not permit any additional discovery of information through runtime type tests are called simple types. Types such as int and string are simple types, as indeed are all .NET value types, .NET delegate types, sealed (final) .NET reference types, F# record types and F# union types . Some .NET types such as obj (i.e., System.Object) typically require the use of upcasts, to generate values of that type, and downcasts and type tests to recover interesting properties of values of that type. That is, the design of these type makes essential use of the fact that values of this type carry dynamic type information. For example, neither the F# type obj (equivalent to System.Object) nor the F# type exn (equivalent to System.Exception) are simple F# types. These require the use of type tests or pattern matching tests in order to recover information from values of these types. Some F# and .NET types are generic (also called polymorphic), e.g the F# type list and the .NET type System.Collections.Generic.Dictionary. Thus the type int list is different from the type string list. In F# code you can write generic instantiations using the ML syntax "string list" or the C# syntax "list<string>". Type variables are written 'a, e.g., 'a list or Dictionary<'key,'a>. Types are frequently only written as annotations and in these situations partially anonymous types can be used: Dictionary<_,_>. F# is statically typed, but frequently code will not contain many type annotations. This is because F# uses type inference to automatically determine the types of expressions. Type inference checks that types "match up" in obvious ways, i.e., that a function definition is consistent and that the definition of a function is sufficiently general to match all the ways in which it is used. Type inference is a global process over an entire source file, or, in the case of entries to F# Interactive, over the scope of a single entry delimited by ';;' terminators. Type inference automatically generalizes code to be as generic as possible. To see the types inferred for the top-level definitions in your program use the -i compiler switch, or hover your mouse over definitions in a supported interactive environment such as Visual Studio. Calls and accesses to .NET and F# methods and properties (collectively known as members) involve a form of type-directed overload resolution, resolved based on left-to-right, outside-to-in analysis of a file, i.e., type information subsequent to the use of an overloaded member is not taken into account for its resolution. In addition, some operators such as + are overloaded. Operator overloading is resolved over the same scope as type inference, typically an entire file. Where operator overloading is not resolved the overloading typically defaults to work over 32-bit integers. Code that uses .NET library calls (and in particular libraries which make heavy use of the dot-notation) will need to be annotated with extra type annotations in order to assist the type inference process. The F# type checker will indicate when further type annotations are needed. Function values are computations that accept arguments and return values. They are the building blocks for most F# programming. Function values are first class (if you like, you can think of them as simple objects) – you may apply function values, store them in data structures, pass them on to other functions such as transforming functions like List.map, "partially apply" them (i.e., apply only some arguments, leaving a residue function), and use them to construct instances of other objects such as .NET delegate values.
Function types are the types given to first-class function values and are written int -> int. They are similar to .NET delegate types, except they aren't given names. All F# function identifiers can be used as first-class function values, and anonymous function values can be created using the (fun ... -> ...) expression form.
F# code commonly uses iterated or curried function values and types where multiple arguments become successive applications of individual values to a function value, e.g., int -> int -> int.
Pattern matching is a way of performing discriminations on values and extracting information from the selected components. Pattern matching can decompose on the concrete structure of types or on the structured results of abstract operations (e.g., on operations that return option types).
MLLib.Pervasives contains function and value definitions that are available by default in all F# code. These include the basic arithmetic operators. The most important of these are shown below.
Pervasives also contains the definitions of the important structural equality and structural comparison operators and functions. For F# types these typically operate according to the term-structure of the values being compared.
The structural and generic nature of these default comparison and equality operators can be seen as follows:
The following two important operators are defined in MLLib.Pervasives:
These are very important in F# code and will be used in many samples you see. They are used to pipeline and compose functions. For example: let allMembers = System.AppDomain.CurrentDomain.GetAssemblies() |> Array.to_list |> List.map (fun a -> a.GetTypes()) |> Array.concat |> Array.to_list |> List.map (fun ty -> ty.GetMembers()) |> Array.concat;; F# type definitions are used to define record types, dicriminated unions, type abbreviations and object model types. F# types can use the type constructors defined by these type definitions. We begin with records.
F# discriminated unions define a new type composed of a fixed number of distinct alternatives. Discriminated unions with only one constructor are often used as a substitute for records (by using only one alternative) during prototyping due to the succinct associated syntax.
F# type abbreviations simply define a macro-like abbreviation for a type or family of types. Type error messages attempt to preserve the use of abbreviations where possible.
The items in a single F# source code file specify a module. The default name of the module is formed by capitalizing the first character of the name of the file that was compiled. Modules can be named explicitly using a module declaration at the head of the file. Modules can themselves contain nested modules. Other F# modules open modules and namespaces they reference, or refer to items from those modules by using long identifiers.
Signatures specify the "public" types and values in a module, i.e., the values and types that can be referenced by other modules. Signatures are stores in signature files with suffixes .mli or .fsi. Signature files should be included on the command line when compiling the modules the constrain.
Members are values associated with a named type and are accessed via a dot-notation. Members can be associated with most F# named types, e.g., records, unions and classes (see below). Members can be static, that is, invoked or accessed through the type name rather than through an instance of the type.
Classes and Interfaces are types that can specify and/or fill a "dictionary" of "abstract" members. Function values are much like simple interfaces where there is only one abstract member (called Invoke). Classes can also carry data and non-abstract members and can provide implementations for abstract members. Other F# named types such as unions and records can also provide implementations of the abstract members on the type System.Object. Members that provide implementations for abstract members are called "default" or "override" members (these are synonymous). Classes can also fill abstract members by inheriting from other classes. Classes also support a new notation. Many classes and interfaces in F# programming arise from using .NET libraries.
Interfaces are similar to classes but only have abstract members. Classes, object expressions and F# record and union types may implement interfaces. An implemented interface acts as its own virtual slot, i.e., a class may provide a default implementation for an interface and may override existing implementations of interfaces.
Object expressions can be used to implement classes and interfaces, and indeed most "fringe" classes in other OO languages can be replaced by object expressions.
See also the information in the Advanced section of the manual. |