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. The fslex and fsyacc 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 standard AssemblyFoldersEx 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 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

#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 AutoOpenAttributes 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 names yield and yield!.

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 and Microsoft.FSharp.Quotations.Typed have been removed
  • Expr<_> is now a subtype of Expr.
  • 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 using new Event<delegate,args>(). You should think of these types as “event implementations”
  • You may trigger events with ev.Trigger(args) and ev.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. Thus string.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 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:



  • Components for compatibility with OCaml and/or earlier release of F#
  • Math components including the Matrix and Vector 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 (11)

  1. – Latest F# Supports Units of Measure (from F# release notes) [ reddit ][ digg ] – Don’s Announcement

  2. sparky says:

    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.

  3. Anders Cui says:

    F# September 2008 CTP发布了,这是F#进展过程的重要一步。

  4. Don Syme’s WebLog on F# and Other Research Projects : The F# September 2008 CTP is now available! F#,

  5. F# の September CTP (英語) がリリースされました。また同時に F# の Developer Center (英語) が公開されています。今回の F# に関しては Don Syme のブログ

  6. It’s been a week now since we posted the F# September 2008 CTP release, and it’s been fantastic to see

  7. 真见 says:

    a minor update to the CTP release

  8. Don Syme vient d’annoncer la sortie d’ une nouvelle CTP de F#. C’est en réalité une mise à jour, corrections

  9. VSLab has just been updated with full support for Visual Studio 2008 Shell and the F# CTP release. It

  10. Anders Cui says:

    在初学F#时,我们可以很随便地将代码放在同一模块内做些尝试或者测试。但我们程序员不该是随便的人,随着项目规模的增大,代码的组织问题会变得越发重要,我们应当越加重视。在VS中进行开发时,整个项目的组织自然地分为了Solution、Project、File三个层次,本文在这三个层次上就代码组织的基本问题做了讨论,写得比较简单,欢迎您来留言讨论 。