|
F# Manual
- Contents
F# and OCaml - differences in detail
F# is similar to the Caml programming language family, and was partially inspired by OCaml. However the F# compiler implementation is a new re-implementation of an Caml-style language, and some relatively minor differences in the implemented languages exist, much as relatively minor differences exist between "Caml Light" and "OCaml". One of the explicit aims of F# is to make it feasible to cross-compile F# code and other ML code such as OCaml code, at least for the core computational components of an application. Cross-compilation between OCaml and F# has been applied to very substantial code bases, including the F# compiler itself, the Abstract IL library and the Static Driver Verifier product shipped by Microsoft. However, F# and OCaml are different languages, and so programmers need to be aware of the differences in order to develop code that be compiled under both systems. The OCaml language is documented at the OCaml website. This section documents the places where F# differs from the OCaml language, i.e. where OCaml code will either not compile or execute with a different semantics when compiled with F#. This list was compiled with reference to OCaml 3.06, so later updates to OCaml may not be mentioned. In addition, the list does not mention differences in the libraries provided. If any further differences are detected please contact the F# team and we will attempt to either fix them or document them. In summary:
Modules, OCaml-style Objects, labels and optional arguments are not supported. F# is based on the core of the OCaml language, and omits these features, partly because many OCaml programmers do not make extensive use of them. Three is a longer discussion of some of the issues in the FAQ. The Set, Map and Hashtbl modules in the library all support module-like operations, although within the core language itself. Some identifiers are now keywords. For example the identifiers upcast, downcast, null and inline are now keywords in F#. The the manual for the full details. Some identifiers such as using are reserved for future use. Top level expressions in modules are not supported. Ocaml lets you write an expression at the top level of a module, e.g. let x = 3;; Printf.printf "x + x = %d" (x + x) F# does not support this, partly because it is a little obsucre. Instead use either the "do" notation (specific to F#) or some other alternative: let x = 3 do Printf.printf "x + x = %d" (x + x) or let x = 3 let () = Printf.printf "x + x = %d" (x + x) Top level initialisation effects occur when .dll is loaded. In OCAML the top-level value bindings are executed ordered first by module, then by definition order with-in a module. In F#, the initialisation sequence with-in a module is still in the definition order, however, module initialisation occurs when the .dll is loaded and initialised. This may not be when the application starts. If code in module M2 depends on initialisation from module M1, then making this explicit via an initialisation call is advisable. Some minor parsing differences. The syntax !x.y.z parses as !(x.y.z) rather than (!x).y.z. OCaml supports the original parsing partly by making case distinctions in the language grammer (uppercase identifiers are module names, lower case identifiers are values, fields and types). However, F# does not lexically distinguish between module names and value names, e.g. upper case identifiers can be used as value names. However, in order to maintain compatibility with OCaml one would then still have to modify the parsing of long identifiers based on case. Although we would prefer to follow the original OCaml syntax, we have decided to depart from OCaml at this point. Other minor parsing differences may also be present in any particular release, since the F# parser is a complete from-scratch re-implementation of the grammar. Strings are Unicode and immutable. This has a number of flow-on effects, for example some of the library signatures differ, e.g. for the basic IO function "input". Chars are "wide characters", giving Unicode support at the expense of breaking the equivalence between characters and bytes. To convert between byte arrays and strings you must call library functions such as the following defined in the Bytearray module in F#'s MLLib: let ascii_to_string (b:byte[]) = System.Text.Encoding.ASCII.GetString(b) let string_to_ascii (s:string) = System.Text.Encoding.ASCII.GetBytes(s) The "include" declaration is not supported. Two toplevel definitions with the same name are not allowed within a module or a module type. let x = 1 let x = 3 will give a compilation error. Within an inner let-binding the existing Caml rule applies, i.e. Type equations may not be abstracted. Type equations (as opposed to new type definitions) may not be hidden by a signature. For example, the compilation unit type x = int constrained by a signature type x will give an error. But type x = X of int constrained by the same signature will not, or type x = int constrained by a signature type x = int will naturally compile. Constraining a module by a signature may not make the values in the module less polymorphic. That is, values in modules may not be more polymorphic than the types given in the corresponding signature. For example, a compile-time error will raised if a module declares let f x = x and the signature declares val f : int -> int The value in the module must be constrained explicitly to be less polymorphic: let f (x:int) = x This can be annoying because extra type annotations are needed, but greatly simplifies compilation. In addition, the code produced turns out to be more efficient. Recursive Data. F#'s version of OCaml's "recursion through data types using 'let rec'" to create "infinite" (i.e. self-referential) data structures is now slightly more restricted. You can't use recursive 'let rec' bindings through immutable fields except in the assembly where the type is declared. This means let rec x = 1 :: x is not supported. This was required to make sure the head/tail fields of lists are marked "private" and "initonly" in the underlying assembly, which is ultimately more important than supporting all variations on this rarely-used feature. However note that type node = { x: int; y: node} let rec myInfiniteNode = {x=1;y=myInfiniteNode} is still supported since the "let rec" occurs in the same assembly as the type definition, and type node = node ref let rec myInfiniteNode = { contents = myInfiniteNode } is supported since "contents" is a mutable field of the type "ref". (When compiling for .NET Versions 1.0 and 1.1 only.) There are two kinds of array types The first is the truly polymorphic set of F# array types, i.e. "'a array". These are correctly polymorphic in the sense that you may write new polymorphic code that manipulates these values. However, because of the lack of support for generics in the CLR these array types are always compiled to the .NET type "object[]". A rich set of polymorphic operations over these array types is provided in the Array module. NET array types are also provided, e.g. "int[]" or "string[]". These are not truly polymorphic in the sense that the F# compiler must be able to infer the exact .NET array types manipulated by any code you write. If you want to write new polymorphic operations over these types then you must duplicate your code for each new array type you wish to manipulate. (This also means you can't use these types as building blocks for new F# data structures such as hashtables - use the polymorphic array types above instead. This is what the inbuilt Hashtable module does.) A rich set of pseudo-polymorphic operations over these array types is provided in the "Microsoft.FSharp.Compatibility.CompatArray" module. These are pseudo-polymorphic because a the code will be duplicated and type-specialized at each callsite. The OCaml C FFI is not supported. Typically writing C# code that specified external calls into DLLs acts as an excellent subsitute. Pinning and allocation can be done by the C# code. Parsers generated with fsyacc have local parser state, while ocamlyacc has a single global parser state code. Parsers generated by fsyacc.exe provide location information within parser actions. However that information is not available globally, but rather is accessed via the functions available on the following local variable which is available in all parser actions: parseState : 'a Microsoft.FSharp.Primitives.ParserState.Provider However, this is not compatible with the parser specifications used with OCamlYacc and similar tools, which make a single parser state available globally. If you wish to use a global parser state (e.g. so your code will cross-compile with OCaml) then you can use the functions in this file. You will need to either generate the parser with '--ml-compatibility' option or add the code Parsing.set_parse_state parseState; at the start of each action of your grammar. The functions below simply report the results of corresponding calls to the latest object specified by a call to set_parse_state. Note that there could be unprotected multi-threaded concurrent access for the parser information, so you should not in general use these functions if there may be more than one parser active, and should instead use the functions directly available from the parseState object. |