Detailed Release Notes for the F# September 2008 CTP release
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
Summary
- The F# Visual Studio project system and language service have been completely re-implemented.
- F# Project Files now use suffix .fsproj and support MSBuild.
- F# Interactive in Visual Studio has been greatly enhanced as a tool window. The menu item is now under
View -> Other Windows
- Enhanced scripting support
- The F# language now supports "Units of Measure" checking and inference
- The command line options for the F# compiler have been simplified and aligned with standard .NET practice, though retain the
--
notation. - The F# library is split into two components.
FSharp.Core.dll
: Contains the core F# libraries, which will be stabilized and versioned infrequently.FSharp.PowerPack.dll
: Contains additional useful F# libraries and tools which will version more frequently, and allow continued innovation on top of the core F# language and libraries. Thefslex
andfsyacc
tools are part of the F# PowerPack. - .NET 1.x is no longer supported. .NET 2.0 or above is needed by this version of the F# compiler and runtime libraries.
- The F# Visual Studio integration now requires Visual Studio 2008 (and may be used with the freely available Visual Studio 2008 Shell).
- #light or #light "off" required in .fs, .fsx and .fsi files
- "AutoOpen" attribute now supported
- 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.
- Sequence and computation expression syntax regularization and simplification
- Improvements to debugging and stepping (requires tailcalls be explicitly disabled)
- Simplified event creation and publication
- Simplified Reflection API
- Simplified quotation language feature and API
- Namespace abbreviations now deprecated
- Language improvements for better software engineering practice
- F# now respects COM automation optional and default parameters
- Operator resolution defaults to resolving to operators defined as static members in relevant argument types if no other definition of the operator is given
- PowerPack support for quotation compilation and evaluation via LINQ expression trees
- The PowerPack lexer generator tool 'fslex' now supports Unicode inputs
- PowerPack support for query evaluation via LINQ expression Trees
- Deletion of features deprecated in previous F# releases
Tool Improvements
The F# Visual Studio Project System and Language Service have been completely re-implemented.
F# Project Files now use suffix .fsproj and support MSBuild.
A converter for the old project file format is available on the F# team blogs.
Enhanced Scripting Support
Script files .fsx
are now governed by slightly different rules to .fs
files.
#r
references are only supported in.fsx
files. For.fs
files use a command line compilation reference or project support in Visual Studio.- On Windows,
#r
references have been enhanced and resolve to assemblies registered in one of the directories indicating installed .NET software under the standardAssemblyFoldersEx
registry key. - In the Visual Studio project system, scripts are given special status. They are no longer included in the build by default (though you may set a property to include them), and must include explicit
#r
references for their dependent DLLs. - Intellisense and interactive type checking now respect the
#load
command - In
.fs
files, warnings are given if one of your top-level expressions has a result that is no explicitly discarded (i.e. a non-unit type) - The
#use
command has been removed, though a startup script may still be specified when using F# Interactive
Compiler command line option normalization
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:
--generate-interface-file
has been renamed to--sig
--no-warn
has been renamed to--nowarn
(a number of other options similarly have hyphens removed)- Optimization flags have changed; instead of
-O3
, now use--optimize
or just-O
To see the full list of compiler options, use fsc.exe -?
.
Language Enhancements and Updates
Units of Measure Inference and Checking
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 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
#light or #light "off" required in .fs, .fsx and .fsi files
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"
.
Subsumption
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:
- Record construction
type Rec = { A : Base ; X : int }
let r = { A = new Derived(7); X = 12}
- Union case construction
type Uni = A of Base | X of int
let u = A(new Derived(7))
- Mutable field assignment
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:
- tuple creation
- list elements
- function binding and "return types"
- if/then/else
Language improvements for better software engineering practice
#nowarn
is now scoped to the end of a file.Namespaces should be opened with a fully-qualified path. A namespace open directive should be provided a fully-qualified namespace path. Thus code like the following now gives a warning:
open System
open Windows // warning now given
open Forms
// open System.Windows.Forms // preferred way
Namespace abbreviations no longer supported
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.
Regular rules for "base"
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() }
Signatures for types with hidden representation must use [<Sealed>]
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
"assert(false)" is no longer given a generic type.
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.
"AutoOpen" attribute now supported
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 AutoOpenAttribute
s taking strings, indicating the given namespaces or modules should be opened implicitly if the assembly is referenced.
F# now respects COM automation optional and default parameters
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.
Simpler operator definition and resolution
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
Slicing operators
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.
Sequence and Computation Expression Syntax Regularization and Simplification
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.
- Imperative actions in computation expressions no longer need to be prefixed by
do
. Like in the rest of the F# language, “do” is no longer required for imperative actions in computation expressions:
let s =
seq { for i in 1..12 do
printfn "i = %A" i
yield i+2 }
- The full range of ‘let’ bindings are permitted in computation expressions. Let bindings in computation expressions now have the same syntax as in the rest of the F# langauge:
let s2 =
async { let rec f x = f x + 1
return 1 }
->
and->>
are now deprecated except in the compact sequence expressions. The two shorthand’s->
and->>
are now deprecated except in the sequence expression short form:seq { for x in c -> f(x) }
. In all other sequence expressions, use the full namesyield
andyield!
.
Inlined Functions now have Generated Code
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.
Improvements to debugging and stepping, though requires tailcalls to be explicitly disabled
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
).
Library Changes
Simplified Reflection API
The Microsoft.FSharp.Reflection
library has been refactored to leverage .NET reflection concepts more broadly.
Simplified Quotations and Quotation API
The Microsoft.FSharp.Quoataions namespace has been refactored to enable provide a more consistent API
- The namespaces and modules are now:
Microsoft.FSharp.Quotations
Microsoft.FSharp.Quotations.Patterns
Microsoft.FSharp.Quotations.DerivedPatterns
Microsoft.FSharp.Quotations.ExprShape
- The modules
Microsoft.FSharp.Quotations.Raw
andMicrosoft.FSharp.Quotations.Typed
have been removed Expr<_>
is now a subtype ofExpr
.- Splicing of values now "comes for free", i.e.
let f (x:int) = <@ 3 + x @>
constructs a quotation with the value of x
substituted at the given place-holder.
- Splicing of expressions now uses
%expr
, so
let f (expr:Expr) = <@ 3 + %expr @>
constructs a quotation with the value of the expression tree expr
is substituted at the given place-holder.
- The primitive constructors have been renamed. “Mk” has been dropped throughout. The full set of primitive constructors is:
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.
Simplified Event Creation and Publication for Events related to a Specific Delegate Type
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:
- You may create regular events using
new Event<args>()
and events for a specific delegate type usingnew Event<delegate,args>()
. You should think of these types as "event implementations" - You may trigger events with
ev.Trigger(args)
andev.Trigger(sender,args)
- You may publish events with
ev.Publish
- The type
IHandlerEvent<_>
has been deleted
For example:
type MyControl() =
let tickEvent = new Event<int>()
member self.React() =
tickEvent.Trigger (4)
member self.OnTick = tickEvent.Publish
Deletion of features deprecated in previous F# releases
- All Unicode symbols deleted from the F# syntax
- IEnumerable.* deleted
- List.of_IEnumerable, List.to_IEnumerable, now deleted in favour of List.of_seq, List.to_seq
- Permutation type deleted, with permutations now represented by functions. The Permutation module remains in the PowerPack
- Func.*, e.g. Func.repeatN deleted
- CompatArray and CompatMatrix modules deleted. Use Array and Array2 instead
- Microsoft.FSharp.Compatibility.CompatMatrix -- .NET 1.x only
- Microsoft.FSharp.Compatibility.CompatArray -- .NET 1.x only
- Microsoft.FSharp.Core.Idioms deleted
- Microsoft.FSharp.Core.Int8 deleted
- Microsoft.FSharp.Core.Int16 deleted
string
is now a function of type'a -> string
. Thusstring.Format
now gives compilation error, use System.String.Format instead. Likewise string.Empty.base
is now used as a keyword and no longer gives a warning- The type of
truncate
is now'a -> 'a
(indeed for any floating point type or a type supporting a static Truncate method) (type ty)
is now typeof<ty> and typedefof<ty>- Abstract types must be marked with [<AbstractClass>]
- Declarations that may be interpreted as either functions or patterns now interpreted as functions, e.g.
let Node(x,l1,r1) = idxToNode(m1) // now always a function definition
The F# PowerPack
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 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:
- Components for compatibility with OCaml and/or earlier release of F#
- Math components including the
Matrix
andVector
types - A component giving the definitions of SI Units of Measure
- Some deprecated components
- Components for supporting evaluation of F# quotations via LINQ and the execution of quotations as LINQ queries
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
PowerPack support for Quotation Compilation and Evaluation via LINQ Expression Trees
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.
PowerPack support for Query Evaluation via LINQ Expression Trees
Note: this PowerPack feature is marked experimental in this release
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.
Comments
Anonymous
August 29, 2008
PingBack from http://blogs.msdn.com/dsyme/archive/2008/08/29/the-f-september-2008-ctp-is-now-available.aspxAnonymous
August 29, 2008
- Latest F# Supports Units of Measure (from F# release notes) [ reddit ][ digg ] - Don's Announcement
Anonymous
August 29, 2008
Thank you all so very much for your hard work. This is absolutely exciting and I hope that I can build a career around F# and .NET in general. While I have gone though most of Expert F# and Foundations of F# some things I still need to work on such as Language Oriented Programming, Workflows, Concurrency and Quotations, as these are my biggest headaches currently. Again, thanks team and keep up the good work.Anonymous
August 29, 2008
F# September 2008 CTP发布了,这是F#进展过程的重要一步。Anonymous
August 29, 2008
Don Syme's WebLog on F# and Other Research Projects : The F# September 2008 CTP is now available! F#,Anonymous
August 31, 2008
F# の September CTP (英語) がリリースされました。また同時に F# の Developer Center (英語) が公開されています。今回の F# に関しては Don Syme のブログAnonymous
September 05, 2008
It’s been a week now since we posted the F# September 2008 CTP release, and it’s been fantastic to seeAnonymous
September 05, 2008
a minor update to the CTP releaseAnonymous
September 05, 2008
Don Syme vient d'annoncer la sortie d' une nouvelle CTP de F#. C'est en réalité une mise à jour, correctionsAnonymous
September 08, 2008
VSLab has just been updated with full support for Visual Studio 2008 Shell and the F# CTP release. ItAnonymous
November 05, 2008
在初学F#时,我们可以很随便地将代码放在同一模块内做些尝试或者测试。但我们程序员不该是随便的人,随着项目规模的增大,代码的组织问题会变得越发重要,我们应当越加重视。在VS中进行开发时,整个项目的组织自然地分为了Solution、Project、File三个层次,本文在这三个层次上就代码组织的基本问题做了讨论,写得比较简单,欢迎您来留言讨论 。