These are the detailed release notes for the F# September 2008 CTP release. The release announcement is here. We will be publishing a known issues list here shortly and augmenting it as new issues are found.
[ Update: The following issues were addressed in the 1.9.6.2 update to the CTP on 6 Sep 2008 3423 type abbreviations to floating point types involving units don’t behave correctly 3424 Quotations.Expr.TupleGet gives exception on zero index 3425 CodeDom invocation of compiler should use '--nologo' 3427 decimal<kg> doesn't support operators 3419 Sequence expressions involving 'let' and 'use' dispose of enumerator too eagerly 3498 Evaluation of a valid quotation expression doesn’t succeed (reported by Credit Suisse) 3491 base variables in object expressions do not follow the same rules as base variables in class types
[ Update: The following issues were addressed in the 1.9.6.2 update to the CTP on 6 Sep 2008
3423 type abbreviations to floating point types involving units don’t behave correctly
3424 Quotations.Expr.TupleGet gives exception on zero index
3425 CodeDom invocation of compiler should use '--nologo'
3427 decimal<kg> doesn't support operators
3419 Sequence expressions involving 'let' and 'use' dispose of enumerator too eagerly
3498 Evaluation of a valid quotation expression doesn’t succeed (reported by Credit Suisse)
3491 base variables in object expressions do not follow the same rules as base variables in class types
A converter for the old project file format is available on the F# team blogs.
Script files .fsx are now governed by slightly different rules to .fs files.
The command line options for the F# compiler have been simplified and aligned with standard .NET practice, though retain the -- notation. Here are a few notable changes:
To see the full list of compiler options, use fsc.exe -?.
The F# CTP sees the first release of units of measure checking and inference, an F# language feature with potential to greatly improve the productivity of scientists, engineers, finance and data analysts who routinely work with floating point numbers. The F# type system now makes it possible to annotate values involving floating point numbers with the units they are measured in.. F# type-checking will then check that calculations correctly preserve units, and type inference will infer a unit-annotated type for code if unit annotations are introduced.
A full introduction to units of measure will be posted as a blog series at or around the time of this release on http://msdn.microsoft.com/fsharp. Some short examples are shown below.
A unit of measure representing kilograms
[<Measure>] type kg
A unit of measure representing seconds
[<Measure>] type s
A constant value in kilograms
let x = 3.0<kg>
A constant value in seconds:
let y = 2.5<s> // y : float<s>
Computing a value of type float<kg/s>
let z = x / y // z : float<kg/s>
Trying to add a value of type float<kg> to a value of type float<s>. This gives: Error: "The unit of measure 's' does not match the unit of measure 'kg'"
let w = x + y
F# code files must now begin with either #light or #light "off" – indicating whether they will use the lightweight F# syntax or not. The most common mode of usage for F# is to use #light. Note: this rule is only enforced by a warning in this version of F#.
Files with extension .ml and .mli do not need the annotation and are implicitly #light "off".
F# functions whose inferred type includes an unsealed type in argument position may now be passed subtypes when called, without the need for explicit upcasts. For example:
type Base() = member b.X = 1 type Derived(i : int) = inherit Base() member d.Y = i let d = new Derived(7) let f (b : Base) = b.X // Call f: Base -> int with an instance of type Derived let res = f d // Use f as a first-class function value of type : Derived -> int let res2 = (f : D -> int)
This aligns members and functions to both do automatic upcasts at calls. This also means that #-types are needed less often. For generic functions, this only applies if the uninstantiated function type contains unsealed types in 'argument position', i.e. as part of a tuple in the curried arguments to a function. Technically speaking, F# supports subsumption by introducing type flexiblity at each point a function value is referred to based on the uninstantiated inferred type of the function.
This change may in some cases, change the inferred types of your functions, though that will normally only be noticeable if you are using signature files.
Automatic upcasting (subsumption) now also applies to a few other language constructs:
type Rec = { A : Base ; X : int } let r = { A = new Derived(7); X = 12}
type Uni = A of Base | X of int let u = A(new Derived(7))
type Rec2 = { mutable A : Base ; X : int } let r2 = { A = new Derived(7); X = 12} r2.A <- new Derived(3)
This change does not change the rules for subsumption on:
open System open Windows // warning now given open Forms // open System.Windows.Forms // preferred way
It is no longer possible to use the module abbreviation syntax to define an abbreviation for a namespace:
module WF = System.Windows.Forms
This is because the element on the right is not a true module.
F# now applies the same rules regarding “base” variables to both object expressions and types, so that code such as
let obj = { new System.Object() as baseObj with
member x.ToString() = "I'm an object: " + baseObj.ToString() }
will generate a deprecation error and should be replaced by
let obj = { new System.Object() with
member x.ToString() = "I'm an object: " + base.ToString() }
In signatures, it is now necessary to use = and [<Sealed>] for types that have methods but whose representation is hidden, e.g.
[<Sealed>] type PositionWithMessage = member Position : Lexing.position member Message : string
instead of
type PositionWithMessage with member Position : Lexing.position member Message : string
In previous versions of F#, an 'OCaml' rule was implemented giving assert(false) a generic type, allowing it to be used as an expression of any type, raising an exception when executed. However, in F# 1.9.4, a change was made to give assert a standard .NET meaning where it becomes a conditional call to System.Diagnostics.Debug.Assert, This means expressions such as assert (1=2) will not fire in your code unless --define DEBUG is specified. This follows standard .NET software engineering practice.
This release removes the 'OCaml' rule for assert(false) since raising an exception for an assertion in non-debug code does not conform to .NET practice.
Modules may be marked with the AutoOpenAttribute, indicating they are implicitly opened if their enclosing namespace or module is opened.
Assemblies may be given one or more AutoOpenAttributes taking strings, indicating the given namespaces or modules should be opened implicitly if the assembly is referenced.
The F# syntax for named and optional arguments may now be used in conjunction with COM automation, and F# code generation respects COM automation defaults for omitted arguments. For example:
chartobject.Chart.ChartWizard(Source = range5, Gallery = XlChartType .xl3DColumn, PlotBy = XlRowCol.xlRows, HasLegend = true, Title = "Sample Chart", CategoryTitle = "Sample Category Type", ValueTitle = "Sample Value Type")
Previously this call required numerous additional "missing value" arguments.
If no other definition of an operator is given, then an operator resolves to a type-directed "member constraint" operator in much the same way as + and *. For example
type Receiver(latestMessage:string) = static member (<--) (receiver:Receiver,message:string) = Receiver(message) static member (-->) (message,receiver:Receiver) = Receiver(message) let r = Receiver "no message" r <-- "Message One" // The 'default' definition of the operator associates it with a static // member through the use of a member constraint "Message Two" --> r
It is now easier to extend F# slicing operators to user-defined types. The slicing operators are compiled as calls to a general set of Get/SetSlice methods. For 1D slices:
e1.[e2opt.. e3opt] --> e1.GetSlice(arg2,arg3) e1.[*] --> e1.GetSlice(None,None)
where argi is Some(eiopt) if eiopt is present and None otherwise.
The syntax for sequence and computation expressions has been simplified. There are no functional changes, but some forms of syntax have been deprecated or removed in favor of a more consistent and regular subset of the syntax.
let s = seq { for i in 1..12 do printfn "i = %A" i yield i+2 }
let s2 = async { let rec f x = f x + 1 return 1 }
In previous versions of F#, function's marked inline did not have code generated. These functions now get code to allow them to be invoked dynamically using .NET reflection.
In some rare cases inlined functions are implemented using unverifiable code. If necessary you can add the NoDynamicInvocationAttribute to a function to disable the generation of code, in which case the function will raise an exception if dynamically invoked.
Debugging and stepping in Visual Studio and other .NET debuggers has been improved. However, reliable stepping is only available if tailcalls are explicitly disabled through an appropriate command line compiler flag (e.g. --optimize- notailcalls).
The Microsoft.FSharp.Reflection library has been refactored to leverage .NET reflection concepts more broadly.
The Microsoft.FSharp.Quoataions namespace has been refactored to enable provide a more consistent API
Microsoft.FSharp.Quotations Microsoft.FSharp.Quotations.Patterns Microsoft.FSharp.Quotations.DerivedPatterns Microsoft.FSharp.Quotations.ExprShape
let f (x:int) = <@ 3 + x @>
constructs a quotation with the value of x substituted at the given place-holder.
let f (expr:Expr) = <@ 3 + %expr @>
constructs a quotation with the value of the expression tree expr is substituted at the given place-holder.
Expr.AddressOf : Expr -> Expr Expr.AddressSet : Expr * Expr -> Expr Expr.Application: Expr * Expr -> Expr Expr.Call : MethodInfo * list<Expr> -> Expr Expr.Call : Expr * MethodInfo * list<Expr> -> Expr Expr.Coerce : Expr * Type -> Expr Expr.IfThenElse : Expr * Expr * Expr -> Expr Expr.ForIntegerRangeLoop: Var * Expr * Expr * Expr -> Expr Expr.FieldGet: FieldInfo -> Expr Expr.FieldGet: obj:Expr * FieldInfo -> Expr Expr.FieldSet: FieldInfo * value:Expr -> Expr Expr.FieldSet: obj:Expr * FieldInfo * value:Expr -> Expr Expr.Lambda : Var * Expr -> Exp Expr.Let : Var * Expr * Expr -> Expr Expr.LetRec : (Var * Expr) list * Expr -> Expr Expr.NewObject: ConstructorInfo * Expr list -> Expr Expr.DefaultValue: Type -> Expr Expr.NewTuple: Expr list -> Expr Expr.NewRecord: Type * Expr list -> Expr Expr.NewArray: Type * Expr list -> Expr Expr.NewDelegate: Type * Var list * Expr -> Expr Expr.NewUnionCase: UnionCaseInfo * Expr list -> Expr Expr.PropGet: Expr * PropertyInfo * ?indexerArgs: Expr list -> Expr Expr.PropGet: PropertyInfo * ?indexerArgs: Expr list -> Expr Expr.PropSet: Expr * PropertyInfo * Expr * ?indexerArgs: Expr list -> Expr Expr.PropSet: PropertyInfo * Expr * ?indexerArgs: Expr list -> Expr Expr.Quote: Expr -> Expr Expr.Sequential: Expr * Expr -> Expr Expr.TryWith: Expr * filterVar:Var * filterBody:Expr * catchVar:Var * catchBody:Expr -> Expr Expr.TryFinally: Expr * Expr -> Expr Expr.TupleGet: Expr * int -> Expr Expr.TypeTest: Expr * Type -> Expr Expr.UnionCaseTest: Expr * UnionCaseInfo -> Expr Expr.Value : obj * Type -> Expr Expr.Value : 'a -> Expr Expr.Var : Var -> Exp Expr.VarSet : Var * Expr -> Expr Expr.WhileLoop : Expr * Expr -> Expr
The primary active patterns for working with expressions are in Microsoft.FSharp.Quotations.Patterns and align with these
Some specific “helper” patterns are contained in Microsoft.FSharp.Quotations.DerivedPatterns. These roughly match those in F# 1.9.4 as follows:
GenericTopDefnApp --> DerivedPatterns.SpecificCall ResolvedTopDefnUse(info,body) --> DerivedPatterns.MethodWithReflectedDefinition(minfo); OR DerivedPatterns.PropertyGetterWithReflectedDefinition(minfo)
The BindingPattern has been renamed to ExprShape and its constituent members renamed and simplified.
The IEvent module is renamed to Event, with the old module still present though deprecated. Apart from that, the existing F# event model of creating a first class event using Event.create is supported unchanged. However, some regularization and simplification has been made for creating events whose handlers have a specific delegate type:
For example:
type MyControl() = let tickEvent = new Event<int>() member self.React() = tickEvent.Trigger (4) member self.OnTick = tickEvent.Publish
let Node(x,l1,r1) = idxToNode(m1) // now always a function definition
The F# PowerPack is a collection of value add and compatibility components for use with F#. The plan of record is that these will not be part of the supported components that make up a release of F#, but will rather become a shared-source project on http://codeplex. You are also encouraged to use the PowerPack code as a guide and a sample should you need to change or extend the functionality implemented there.
As a result the F# PowerPack contains a mixture of components:
If you wish to program without a dependency on the PowerPack, then some changes may be necessary. Most are marked by warnings from the compiler. For example:
Sys.argv --> System.Environment.GetCommandLineArgs() Char.code --> int Char.chr --> char Filename.* --> Use System.IO.Path methods instead Bytearray --> Array
Note: this PowerPack feature is marked experimental in this release
The assembly FSharp.PowerPack.Linq.dll contains support for the translating F# quotation expressions to LINQ expression trees. Since LINQ expression trees can be dynamically compiled this allows a form of compilation end execution for F# quotation expressions. For example:
open Microsoft.FSharp.Linq.QuotationEvaluation let q = <@ 1 + 1 @> q.ToLinqExpression () // the corresponding LINQ expression is then shown q.Compile () // a value of type 'unit -> int' is returned q.Eval () // the value 2 is returned
Note: The performance goal of quotation execution via LINQ is not to match that of compiled F# code: performance can be 5X slower or more. In particular, the LINQ dynamic expression compiler has limitations that require workarounds and overhead in the Quotation-to-LINQ expression tree translator. Instead, the purpose of the translator is to provide a generic way of dynamically evaluating leaf fragments of quotations while the 'heavy lifting' is performed by other core routines. A classic use of this kind of mechanism is in the implementation of portions of a query evaluator, where the core query is executed on a relational database and additional, cheap post-processing is performed on the client.
Note: Some size limitations exist in the implementation of this component. In particular, closures may not contain more than 20 free variables, and mutually recursive function groups may involve at most 8 mutually recursive functions.
The assembly FSharp.PowerPack.Linq.dll contains support for executing F# quotation expressions as queries under the LINQ IQueryable paradigm. For example:
open Microsoft.FSharp.Linq.Query type Customer = { Name:string; Data: int; Cost:float; Sizes: int list } let c1 = { Name="Don"; Data=6; Cost=6.2; Sizes=[1;2;3;4] } let c2 = { Name="Peter"; Data=7; Cost=4.2; Sizes=[10;20;30;40] } let c3 = { Name="Freddy"; Data=8; Cost=9.2; Sizes=[11;12;13;14] } let c4 = { Name="Freddi"; Data=8; Cost=1.0; Sizes=[21;22;23;24] } let data = [c1;c2;c3;c4] let db = System.Linq.Queryable.AsQueryable<Customer>(data |> List.to_seq) // Now execute a query <@ seq { for i in db do for j in db do yield (i.Name,j.Name) } @>
Here a query is an expression of a particular form involving manipulations on F# sequences. The form of sequences accepted is shown below:
query <@ qexpr-with-post-processing @> qexpr-with-post-processing = | qexpr | [ comp-qexpr ] | [| comp-qexpr |] | qexpr |> Seq.length | qexpr |> Seq.hd | qexpr |> Seq.find select-expr | qexpr |> Seq.max | qexpr |> Seq.min | qexpr |> Seq.average | qexpr |> Seq.average_by select-expr | qexpr |> Seq.sum | qexpr |> Seq.sum _by select-expr | qexpr-with-post-processing |> Query.max_by (fun v -> select-expr[v]) | qexpr-with-post-processing |> Query.min_by (fun v -> select-expr[v]) for aggregation types float, float32, decimal | macro reflected-definition reflected-definition args let v = e in qexpr-with-post-processing let f args = e in qexpr-with-post-processing | also prefix application versions of piped syntax qexpr = | expr: IQueryable<_> // db.Customers, f x | seq { comp-qexpr } | if expr then qexpr1 else qexpr2 | match expr with qpati -> qexpri | Seq.empty | qexpr |> Seq.map_concat (fun v -> qexpr) | qexpr |> Seq.filter select-expr | qexpr |> Seq.map select-expr | qexpr |> Seq.take expr | qexpr |> Seq.sort | qexpr |> Seq.distinct | qexpr |> Seq.sort_by select-expr | qexpr |> Seq.delay qexpr | qexpr |> Seq.exists | qexpr |> Seq.for_all | Seq.append qexpr1 else qexpr2 | qexpr |> Query.group_by select-expr | qexpr |> Query.contains expr | Query.join qexpr1 qexpr2 select-expr select-expr select-expr | Query.group_join qexpr1 qexpr2 select-expr select-expr select-expr | macro | also prefix application versions of piped syntax comp-qexpr = | for qpat in qexpr do comp-qexpr // Note: only simple variable patterns should be used | yield expr
The semantics of query execution are given by translation to an LINQ expression involving uses of the IQueryable type and executing that expression. This in turn will depend on the query provider implementation.