diff --git a/docs/fsharp/style-guide/component-design-guidelines.md b/docs/fsharp/style-guide/component-design-guidelines.md new file mode 100644 index 0000000000000..6e816fcf5ad21 --- /dev/null +++ b/docs/fsharp/style-guide/component-design-guidelines.md @@ -0,0 +1,880 @@ +--- +title: F# component design guidelines +description: Learn the guidelines for writing F# components intended for consumption by other callers. +ms.date: 05/14/2018 +--- +# F# component design guidelines + +This document is a set of component design guidelines for F# programming, based on the F# Component Design Guidelines, v14, Microsoft Research, and [another version](https://fsharp.org/specs/component-design-guidelines/) originally curated and maintained by the F# Software Foundation. + +This document assumes you are familiar with F# programming. Many thanks to the F# community for their contributions and helpful feedback on various versions of this guide. + +## Overview + +This document looks at some of the issues related to F# component design and coding. A component can mean any of the following: + +* A layer in your F# project that has external consumers within that project. +* A library intended for consumption by F# code across assembly boundaries. +* A library intended for consumption by any .NET language across assembly boundaries. +* A library intended for distribution via a package repository, such as [NuGet](https://nuget.org). + +Techniques described in this article follow the [Five principles of good F# code](index.md#five-principles-of-good-f-code), and thus utilize both functional and object programming as appropriate. + +Regardless of the methodology, the component and library designer faces a number of practical and prosaic issues when trying to craft an API that is most easily usable by developers. Conscientious application of the [.NET Library Design Guidelines](../../standard/design-guidelines/index.md) will steer you towards creating a consistent set of APIs that are pleasant to consume. + +## General guidelines + +There are a few universal guidelines that apply to F# libraries, regardless of the intended audience for the library. + +### Learn the .NET Library Design Guidelines + +Regardless of the kind of F# coding you are doing, it is valuable to have a working knowledge of the [.NET Library Design Guidelines](../../standard/design-guidelines/index.md). Most other F# and .NET programmers will be familiar with these guidelines, and expect .NET code to conform to them. + +The .NET Library Design Guidelines provide general guidance regarding naming, designing classes and interfaces, member design (properties, methods, events, etc.) and more, and are a useful first point of reference for a variety of design guidance. + +### Add XML documentation comments to your code + +XML documentation on public APIs ensure that users can get great Intellisense and Quickinfo when using these types and members, and enable building documentation files for the library. See the [XML Documentation](../language-reference/xml-documentation.md) about various xml tags that can be used for additional markup within xmldoc comments. + +```fsharp +/// A class for representing (x,y) coordinates +type Point = + + /// Computes the distance between this point and another + member DistanceTo : otherPoint:Point -> float +``` + +You can use either the short form XML comments (`/// comment`), or standard XML comments (`///comment`). + +### Consider using explicit signature files (.fsi) for stable library and component APIs + +Using explicit signatures files in an F# library provides a succinct summary of public API, which both helps to ensure that you know the full public surface of your library, as well as provides a clean separation between public documentation and internal implementation details. Note that signature files add friction to changing the public API, by requiring changes to be made in both the implementation and signature files. As a result, signature files should typically only be introduced when an API has become solidified and is no longer expected to change significantly. + +### Always follow best practices for using strings in .NET + +Follow [Best Practices for Using Strings in .NET](../../standard/base-types/best-practices-strings.md) guidance. In particular, always explicitly state *cultural intent* in the conversion and comparison of strings (where applicable). + +## Guidelines for F#-facing libraries + +This section presents recommendations for developing public F#-facing libraries; that is, libraries exposing public APIs that are intended to be consumed by F# developers. There are a variety of library-design recommendations applicable specifically to F#. In the absence of the specific recommendations that follow, the .NET Library Design Guidelines are the fallback guidance. + +### Naming conventions + +#### Use .NET naming and capitalization conventions + +The following table follows .NET naming and capitalization conventions. There are small additions to also include F# constructs. + +| Construct | Case | Part | Examples | Notes | +|-----------|------|------|----------|-------| +| Concrete types | PascalCase | Noun/ adjective | List, Double, Complex | Concrete types are structs, classes, enumerations, delegates, records, and unions. Though type names are traditionally lowercase in OCaml, F# has adopted the .NET naming scheme for types. +| DLLs | PascalCase | | Fabrikam.Core.dll | | +| Union tags | PascalCase | Noun | Some, Add, Success | Do not use a prefix in public APIs. Optionally use a prefix when internal, such as ```type Teams = TAlpha | TBeta | TDelta.``` | +| Event | PascalCase | Verb | ValueChanged / ValueChanging | | +| Exceptions | PascalCase | | WebException | Name should end with “Exception”. | +| Field | PascalCase | Noun | CurrentName | | +| Interface types | PascalCase | Noun/ adjective | IDisposable | Name should start with “I”. | +| Method | PascalCase | Verb | ToString | | +| Namespace | PascalCase | | Microsoft.FSharp.Core | Generally use `.[.]`, though drop the organization if the technology is independent of organization. | +| Parameters | camelCase | Noun | typeName, transform, range | | +| let values (internal) | camelCase or PascalCase | Noun/ verb | getValue, myTable | +| let values (external) | camelCase or PascalCase | Noun/verb | List.map, Dates.Today | let-bound values are often public when following traditional functional design patterns. However, generally use PascalCase when the identifier can be used from other .NET languages. | +| Property | PascalCase | Noun/ adjective | IsEndOfFile, BackColor | Boolean properties generally use Is and Can and should be affirmative, as in IsEndOfFile, not IsNotEndOfFile. + +#### Avoid abbreviations + +The .NET guidelines discourage the use of abbreviations (for example, “use `OnButtonClick` rather than `OnBtnClick`”). Common abbreviations, such as `Async` for “Asynchronous”, are tolerated. This guideline is sometimes ignored for functional programming; for example, `List.iter` uses an abbreviation for “iterate”. For this reason, using abbreviations tends to be tolerated to a greater degree in F#-to-F# programming, but should still generally be avoided in public component design. + +#### Avoid casing name collisions + +The .NET guidelines say that casing alone cannot be used to disambiguate name collisions, since some client languages (for example, Visual Basic) are case-insensitive. + +#### Use acronyms where appropriate + +Acronyms such as XML are not abbreviations and are widely used in .NET libraries in uncapitalized form (Xml). Only well-known, widely recognized acronyms should be used. + +#### Use PascalCase for generic parameter names + +Do use PascalCase for generic parameter names in public APIs, including for F#-facing libraries. In particular, use names like `T`, `U`, `T1`, `T2` for arbitrary generic parameters, and when specific names make sense, then for F#-facing libraries use names like `Key`, `Value`, `Arg` (but not for example, `TKey`). + +#### Use either PascalCase or camelCase for public functions and values in F# modules + +camelCase is used for public functions that are designed to be used unqualified (for example, `invalidArg`), and for the “standard collection functions” (for example, List.map). In both these cases, the function names act much like keywords in the language. + +### Object, Type, and Module design + +#### Use namespaces or modules to contain your types and modules + +Each F# file in a component should begin with either a namespace declaration or a module declaration. + +```fsharp +namespace Fabrikam.BasicOperationsAndTypes + +type ObjectType1() = + ... + +type ObjectType2() = + ... + +module CommonOperations = + ... +``` + +or + +```fsharp +module Fabrikam.BasicOperationsAndTypes + +type ObjectType1() = + ... + +type ObjectType2() = + ... + +module CommonOperations = + ... +``` + +The differences between using modules and namespaces to organize code at the top level are as follows: + +* Namespaces can span multiple files +* Namespaces cannot contain F# functions unless they are within an inner module +* The code for any given module must be contained within a single file +* Top-level modules can contain F# functions without the need for an inner module + +The choice between a top-level namespace or module affects the compiled form of the code, and thus will affect the view from other .NET languages should your API eventually be consumed outside of F# code. + +#### Use methods and properties for operations intrinsic to object types + +When working with objects, it is best to ensure that consumable functionality is implemented as methods and properties on that type. + +```fsharp +type HardwareDevice() = + + member this.ID = ... + + member this.SupportedProtocols = ... + +type HashTable<'Key,'Value>(comparer: IEqualityComparer<'Key>) = + + member this.Add(key, value) = ... + + member this.ContainsKey(key) = ... + + member this.ContainsValue(value) = ... +``` + +The bulk of functionality for a given member need not necessarily be implemented in that member, but the consumable piece of that functionality should be. + +#### Use classes to encapsulate mutable state + +In F#, this only needs to be done where that state is not already encapsulated by another language construct, such as a closure, sequence expression, or asynchronous computation. + +```fsharp +type Counter() = + // let-bound values are private in classes. + let mutable count = 0 + + member this.Next() = + count <- count + 1 + count +``` + +#### Use interfaces to group related operations + +Use interface types to represent a set of operations. This is preferred to other options, such as tuples of functions or records of functions. + +```fsharp +type Serializer<'T> = + abstract Serialize<'T> : preserveRefEq: bool -> value: 'T -> string + abstract Deserialize<'T> : preserveRefEq: bool -> pickle: string -> 'T +``` + +In preference to: + +```fsharp +type Serializer<'T> = { + Serialize : bool -> 'T -> string + Deserialize : bool -> string -> 'T +} +``` + +Interfaces are first-class concepts in .NET, which you can use to achieve what Functors would normally give you. Additionally, they can be used to encode existential types into your program, which records of functions cannot. + +#### Use a module to group functions which act on collections + +When you define a collection type, consider providing a standard set of operations like `CollectionType.map` and `CollectionType.iter`) for new collection types. + +```fsharp +module CollectionType = + let map f c = + ... + let iter f c = + ... +``` + +If you include such a module, follow the standard naming conventions for functions found in FSharp.Core. + +#### Use a module to group functions for common, canonical functions, especially in math and DSL libraries + +For example, `Microsoft.FSharp.Core.Operators` is an automatically opened collection of top-level functions (like `abs` and `sin`) provided by FSharp.Core.dll. + +Likewise, a statistics library might include a module with functions `erf` and `erfc`, where this module is designed to be explicitly or automatically opened. + +#### Consider using RequireQualifiedAccess and carefully apply AutoOpen attributes + +Adding the `[]` attribute to a module indicates that the module may not be opened and that references to the elements of the module require explicit qualified access. For example, the `Microsoft.FSharp.Collections.List` module has this attribute. + +This is useful when functions and values in the module have names that are likely to conflict with names in other modules. Requiring qualified access can greatly increase the long-term maintainability and evolvability of a library. + +Adding the `[]` attribute to a module means the module will be opened when the containing namespace is opened. The `[]` attribute may also be applied to an assembly to indicate a module that is automatically opened when the assembly is referenced. + +For example, a statistics library **MathsHeaven.Statistics** might contain a `module MathsHeaven.Statistics.Operators` containing functions `erf` and `erfc`. It is reasonable to mark this module as `[]`. This means `open MathsHeaven.Statistics` will also open this module and bring the names `erf` and `erfc` into scope. Another good use of `[]` is for modules containing extension methods. + +Overuse of `[]` leads to polluted namespaces, and the attribute should be used with care. For specific libraries in specific domains, judicious use of `[]` can lead to better usability. + +#### Consider defining operator members on classes where using well-known operators is appropriate + +Sometimes classes are used to model mathematical constructs such as Vectors. When the domain being modeled has well-known operators, defining them as members intrinsic to the class is helpful. + +```fsharp +type Vector(x:float) = + + member v.X = x + + static member (*) (vector:Vector, scalar:float) = Vector(vector.X * scalar) + + static member (+) (vector1:Vector, vector2:Vector) = Vector(vector1.X + vector2.X) + +let v = Vector(5.0) + +let u = v * 10.0 +``` + +This guidance corresponds to general .NET guidance for these types. However, it can be additionally important in F# coding as this allows these types to be used in conjunction with F# functions and methods with member constraints, such as List.sumBy. + +#### Consider using CompiledName to provide a .NET-friendly name for other .NET language consumers + +Sometimes you may wish to name something in one style for F# consumers (such as a static member in lower case so that it appears as if it were a module-bound function), but have a different style for the name when it is compiled into an assembly. You can use the `[]` attribute to provide a different style for non F# code consuming the assembly. + +```fsharp +type Vector(x:float, y:float) = + + member v.X = x + member v.Y = y + + [] + static member create x y = Vector (x, y) + +let v = Vector.create 5.0 3.0 +``` + +By using `[]`, you can use .NET naming conventions for non F# consumers of the assembly. + +#### Use method overloading for member functions, if doing so provides a simpler API + +Method overloading is a powerful tool for simplifying an API that may need to perform similar functionality, but with different options or arguments. + +```fsharp +type Logger() = + + member this.Log(message) = + ... + member this.Log(message, retryPolicy) = + ... +``` + +In F#, it is more common to overload on number of arguments rather than types of arguments. + +#### Hide the representations of record and union types if the design of these types is likely to evolve + +Avoid revealing concrete representations of objects. For example, the concrete representation of values is not revealed by the external, public API of the .NET library design. At run time, the Common Language Runtime knows the committed implementation that will be used throughout execution. However, compiled code doesn't itself pick up dependencies on the concrete representation. + +#### Avoid the use of implementation inheritance for extensibility + +In F#, implementation inheritance is rarely used. Furthermore, inheritance hierarchies are often complex and difficult to change when new requirements arrive. Inheritance implementation still exists in F# for compatibility and rare cases where it is the best solution to a problem, but alternative techniques should be sought in your F# programs when designing for polymorphism, such as interface implementation. + +### Function and member signatures + +#### Use tuples for return values when returning a small number of multiple unrelated values + +Here is a good example of using a tuple in a return type: + +```fsharp +val divrem : BigInteger -> BigInteger -> BigInteger * BigInteger +``` + +For return types containing many components, or where the components are related to a single identifiable entity, consider using a named type instead of a tuple. + +#### Use `Async` for async programming at F# API boundaries + +If there is a corresponding synchronous operation named `Operation` that returns a `T`, then the async operation should be named `AsyncOperation` if it returns `Async` or `OperationAsync` if it returns `Task`. For commonly used .NET types that expose Begin/End methods, consider using `Async.FromBeginEnd` to write extension methods as a façade to provide the F# async programming model to those .NET APIs. + +```fsharp +type SomeType = + member this.Compute(x:int) : int = + ... + member this.AsyncCompute(x:int) : Async = + ... + +type System.ServiceModel.Channels.IInputChannel with + member this.AsyncReceive() = + ... +``` + +### Exceptions + +Exceptions are exceptional in .NET; that is, they should not occur frequently at runtime. When they do, the information they contain is valuable. Exceptions are a core first class concept of .NET; it hence follows that appropriate application of the Exceptions should be used as part of the design of the interface of a component. + +#### Follow the .NET guidelines for exceptions + +The [.NET Library Design Guidelines](../../standard/design-guidelines/exceptions.md) give excellent advice on the use of exceptions in the context of all .NET programming. Some of these guidelines are as follows: + +* Do not use exceptions for normal flow of control. Although this technique is often used in languages such as OCaml, it is bug-prone and can be inefficient on .NET. Instead, consider returning a `None` option value to indicate a failure that is a common or expected occurrence. + +* Document exceptions thrown by your components when a function is used incorrectly. + +* Where possible, employ existing exceptions from the System namespaces. Avoid , though. + +* Do not throw when it will escape to user code. This includes avoiding the use of `failwith`, `failwithf`, which are handy functions for use in scripting and for code under development, but should be removed from F# library code in favor of throwing a more specific exception type. + +* Use `nullArg`, `invalidArg`, and `invalidOp` as the mechanism to throw , , and when appropriate. + +#### Consider using option values for return types when failure is not an exceptional scenario + +The .NET approach to exceptions is that they should be “exceptional”; that is, they should occur relatively infrequently. However, some operations (for example, searching a table) may fail frequently. F# option values are an excellent way to represent the return types of these operations. These operations conventionally start with the name prefix “try”: + +```fsharp +// bad: throws exception if no element meets criteria +member this.FindFirstIndex(pred : 'T -> bool) : int = + ... + +// bad: returns -1 if no element meets criteria +member this.FindFirstIndex(pred : 'T -> bool) : int = + ... + +// good: returns None if no element meets criteria +member this.TryFindFirstIndex(pred : 'T -> bool) : int option = + ... +``` + +### Extension Members + +#### Carefully apply F# extension members in F#-to-F# components + +F# extension members should generally only be used for operations that are in the closure of intrinsic operations associated with a type in the majority of its modes of use. One common use is to provide APIs that are more idiomatic to F# for various .NET types: + +```fsharp +type System.ServiceModel.Channels.IInputChannel with + member this.AsyncReceive() = + Async.FromBeginEnd(this.BeginReceive, this.EndReceive) + +type System.Collections.Generic.IDictionary<'Key,'Value> with + member this.TryGet key = + let ok, v = this.TryGetValue key + if ok then Some v else None +``` + +### Union Types + +#### Use discriminated unions instead of class hierarchies for tree-structured data + +Tree-like structures are recursively defined. This is awkward with inheritance, but elegant with Discriminated Unions. + +```fsharp +type BST<'T> = + | Empty + | Node of 'T * BST<'T> * BST<'T> +``` + +Representing tree-like data with Discriminated Unions also allows you to benefit from exhaustiveness in pattern matching. + +#### Use `[]` on union types whose case names are not sufficiently unique + +You may find yourself in a domain where the same name is the best name for different things, such as Discriminated Union cases. You can use `[]` to disambiguate case names in order to avoid triggering confusing errors due to shadowing dependent on the ordering of `open` statements + +#### Hide the representations of discriminated unions for binary compatible APIs if the design of these types is likely to evolve + +Unions types rely on F# pattern-matching forms for a succinct programming model. As mentioned previously, you should avoid revealing concrete data representations if the design of these types is likely to evolve. + +For example, the representation of a discriminated union can be hidden using a private or internal declaration, or by using a signature file. + +```fsharp +type Union = + private + | CaseA of int + | CaseB of string +``` + +If you reveal discriminated unions indiscriminately, you may find it hard to version your library without breaking user code. Instead, consider revealing one or more active patterns to permit pattern matching over values of your type. + +Active patterns provide an alternate way to provide F# consumers with pattern matching while avoiding exposing F# Union Types directly. + +### Inline Functions and Member Constraints + +#### Define generic numeric algorithms using inline functions with implied member constraints and statically resolved generic types + +Arithmetic member constraints and F# comparison constraints are a standard for F# programming. For example, consider the following code: + +```fsharp +let inline highestCommonFactor a b = + let rec loop a b = + if a = LanguagePrimitives.GenericZero<_> then b + elif a < b then loop a (b - a) + else loop (a - b) b + loop a b +``` + +The type of this function is as follows: + +```fsharp +val inline highestCommonFactor : ^T -> ^T -> ^T + when ^T : (static member Zero : ^T) + and ^T : (static member ( - ) : ^T * ^T -> ^T) + and ^T : equality + and ^T : comparison +``` + +This is a suitable function for a public API in a mathematical library. + +#### Avoid using member constraints to simulate type classes and duck typing + +It is possible to simulate “duck typing” using F# member constraints. However, members that make use of this should not in general be used in F#-to-F# library designs. This is because library designs based on unfamiliar or non-standard implicit constraints tend to cause user code to become inflexible and tied to one particular framework pattern. + +Additionally, there is a good chance that heavy use of member constraints in this manner can result in very long compile times. + +### Operator Definitions + +#### Avoid defining custom symbolic operators + +Custom operators are essential in some situations and are highly useful notational devices within a large body of implementation code. For new users of a library, named functions are often easier to use. In addition, custom symbolic operators can be hard to document, and users find it more difficult to look up help on operators, due to existing limitations in IDE and search engines. + +As a result, it is best to publish your functionality as named functions and members, and additionally expose operators for this functionality only if the notational benefits outweigh the documentation and cognitive cost of having them. + +### Units of Measure + +#### Carefully use units of measure for added type safety in F# code + +Additional typing information for units of measure is erased when viewed by other .NET languages. Be aware that .NET components, tools, and reflection will see types-sans-units. For example, C# consumers will see `float` rather than `float`. + +### Type Abbreviations + +#### Carefully use type abbreviations to simplify F# code + +.NET components, tools, and reflection will not see abbreviated names for types. Significant usage of type abbreviations can also make a domain appear more complex than it actually is, which could confuse consumers. + +#### Avoid type abbreviations for public types whose members and properties should be intrinsically different to those available on the type being abbreviated + +In this case, the type being abbreviated reveals too much about the representation of the actual type being defined. Instead, consider wrapping the abbreviation in a class type or a single-case discriminated union (or, when performance is essential, consider using a struct type to wrap the abbreviation). + +For example, it is tempting to define a multi-map as a special case of an F# map, for example: + +```fsharp +type MultiMap<'Key,'Value> = Map<'Key,'Value list> +``` + +However, the logical dot-notation operations on this type are not the same as the operations on a Map – for example, it is reasonable that the lookup operator map.[key] return the empty list if the key is not in the dictionary, rather than raising an exception. + +## Guidelines for libraries for Use from other .NET Languages + +When designing libraries for use from other .NET languages, it is important to adhere to the [.NET Library Design Guidelines](../../standard/design-guidelines/index.md). In this document, these libraries are labeled as vanilla .NET libraries, as opposed to F#-facing libraries that use F# constructs without restriction. Designing vanilla .NET libraries means providing familiar and idiomatic APIs consistent with the rest of the .NET Framework by minimizing the use of F#-specific constructs in the public API. The rules are explained in the following sections. + +### Namespace and Type sesign (for libraries for use from other .NET Languages) + +#### Apply the .NET naming conventions to the public API of your components + +Pay special attention to the use of abbreviated names and the .NET capitalization guidelines. + +```fsharp +type pCoord = ... + member this.theta = ... + +type PolarCoordinate = ... + member this.Theta = ... +``` + +#### Use namespaces, types, and members as the primary organizational structure for your components + +All files containing public functionality should begin with a `namespace` declaration, and the only public-facing entities in namespaces should be types. Do not use F# modules. + +Use non-public modules to hold implementation code, utility types, and utility functions. + +Static types should be preferred over modules, as they allow for future evolution of the API to use overloading and other .NET API design concepts that may not be used within F# modules. + +For example, in place of the following public API: + +```fsharp +module Fabrikam + +module Utilities = + let Name = "Bob" + let Add2 x y = x + y + let Add3 x y z = x + y + z +``` + +Consider instead: + +```fsharp +namespace Fabrikam + +[] +type Utilities = + static member Name = "Bob" + static member Add(x,y) = x + y + static member Add(x,y,z) = x + y + z +``` + +#### Use F# record types in vanilla .NET APIs if the design of the types won't evolve + +F# record types compile to a simple .NET class. These are suitable for some simple, stable types in APIs. You should consider using the `[]` and `[]` attributes to suppress the automatic generation of interfaces. Also avoid using mutable record fields in vanilla .NET APIs as these exposes a public field. Always consider whether a class would provide a more flexible option for future evolution of the API. + +For example, the following F# code exposes the public API to a C# consumer: + +F#: + +```fsharp +[] +type MyRecord = + { FirstThing : int + SecondThing : string } +``` + +C#: + +```csharp +public sealed class MyRecord +{ + public MyRecord(int firstThing, string secondThing); + public int FirstThing { get; } + public string SecondThing { get; } +} +``` + +#### Hide the representation of F# union types in vanilla .NET APIs + +F# union types are not commonly used across component boundaries, even for F#-to-F# coding. They are an excellent implementation device when used internally within components and libraries. + +When designing a vanilla .NET API, consider hiding the representation of a union type by using either a private declaration or a signature file. + +```fsharp +type PropLogic = + private + | And of PropLogic * PropLogic + | Not of PropLogic + | True +``` + +You may also augment types that use a union representation internally with members to provide a desired .NET-facing API. + +```fsharp +type PropLogic = + private + | And of PropLogic * PropLogic + | Not of PropLogic + | True + + /// A public member for use from C# + member x.Evaluate = + match x with + | And(a,b) -> a.Evaluate && b.Evaluate + | Not a -> not a.Evaluate + | True -> true + + /// A public member for use from C# + static member CreateAnd(a,b) = And(a,b) +``` + +#### Design GUI and other components using the design patterns of the framework + +There are many different frameworks available within .NET, such as WinForms, WPF, and ASP.NET. Naming and design conventions for each should be used if you are designing components for use in these frameworks. For example, for WPF programming, adopt WPF design patterns for the classes you are designing. For models in user interface programming, use design patterns such as events and notification-based collections such as those found in . + +### Object and Member design (for libraries for use from other .NET Languages) + +#### Use the CLIEvent attribute to expose .NET events + +Construct a `DelegateEvent` with a specific .NET delegate type that takes an object and `EventArgs` (rather than an `Event`, which just uses the `FSharpHandler` type by default) so that the events are published in the familiar way to other .NET languages. + +```fsharp +type MyBadType() = + let myEv = new Event() + + [] + member this.MyEvent = myEv.Publish + +type MyEventArgs(x:int) = + inherit System.EventArgs() + member this.X = x + + /// A type in a component designed for use from other .NET languages +type MyGoodType() = + let myEv = new DelegateEvent>() + + [] + member this.MyEvent = myEv.Publish +``` + +#### Expose asynchronous operations as methods which return .NET tasks + +Tasks are used in .NET to represent active asynchronous computations. Tasks are in general less compositional than F# `Async` objects, since they represent “already executing” tasks and can’t be composed together in ways that perform parallel composition, or which hide the propagation of cancellation signals and other contextual parameters. + +However, despite this, methods which return Tasks are the standard representation of asynchronous programming on .NET. + +```fsharp +/// A type in a component designed for use from other .NET languages +type MyType() = + + let compute (x: int) : Async = async { ... } + + member this.ComputeAsync(x) = compute x |> Async.StartAsTask +``` + +You will frequently also want to accept an explicit cancellation token: + +```fsharp +/// A type in a component designed for use from other .NET languages +type MyType() = + let compute(x:int) : Async = async { ... } + member this.ComputeAsTask(x, cancellationToken) = Async.StartAsTask(compute x, cancellationToken) +``` + +#### Use .NET delegate types instead of F# function types + +Here “F# function types” mean “arrow” types like `int -> int`. + +Instead of this: + +```fsharp +member this.Transform(f:int->int) = + ... +``` + +Do this: + +```fsharp +member this.Transform(f:Func) = + ... +``` + +The F# function type appears as `class FSharpFunc` to other .NET languages, and is less suitable for language features and tooling that understand delegate types. When authoring a higher-order method targeting .NET Framework 3.5 or higher, the `System.Func` and `System.Action` delegates are the right APIs to publish to enable .NET developers to consume these APIs in a low-friction manner. (When targeting .NET Framework 2.0, the system-defined delegate types are more limited; consider using predefined delegate types such as `System.Converter` or defining a specific delegate type.) + +On the flip side, .NET delegates are not natural for F#-facing libraries (see the next Section on F#-facing libraries). As a result, a common implementation strategy when developing higher-order methods for vanilla .NET libraries is to author all the implementation using F# function types, and then create the public API using delegates as a thin façade atop the actual F# implementation. + +#### Use the TryGetValue pattern instead of returning F# option values, and prefer method overloading to taking F# option values as arguments + +Common patterns of use for the F# option type in APIs are better implemented in vanilla .NET APIs using standard .NET design techniques. Instead of returning an F# option value, consider using the bool return type plus an out parameter as in the "TryGetValue" pattern. And instead of taking F# option values as parameters, consider using method overloading or optional arguments. + +```fsharp +member this.ReturnOption() = Some 3 + +member this.ReturnBoolAndOut(outVal : byref) = + outVal <- 3 + true + +member this.ParamOption(x : int, y : int option) = + match y with + | Some y2 -> x + y2 + | None -> x + +member this.ParamOverload(x : int) = x + +member this.ParamOverload(x : int, y : int) = x + y +``` + +#### Use the .NET collection interface types IEnumerable\ and IDictionary\ for parameters and return values + +Avoid the use of concrete collection types such as .NET arrays `T[]`, F# types `list`, `Map` and `Set`, and .NET concrete collection types such as `Dictionary`. The .NET Library Design Guidelines have good advice regarding when to use various collection types like `IEnumerable`. Some use of arrays (`T[]`) is acceptable in some circumstances, on performance grounds. Note especially that `seq` is just the F# alias for `IEnumerable`, and thus seq is often an appropriate type for a vanilla .NET API. + +Instead of F# lists: + +```fsharp +member this.PrintNames(names : string list) = + ... +``` + +Use F# sequences: + +```fsharp +member this.PrintNames(names : seq) = + ... +``` + +#### Use the unit type as the only input type of a method to define a zero-argument method, or as the only return type to define a void-returning method + +Avoid other uses of the unit type. These are good: + +```fsharp +✔ member this.NoArguments() = 3 + +✔ member this.ReturnVoid(x : int) = () +``` + +This is bad: + +```fsharp +member this.WrongUnit( x:unit, z:int) = ((), ()) +``` + +#### Check for null values on vanilla .NET API boundaries + +F# implementation code tends to have fewer null values, due to immutable design patterns and restrictions on use of null literals for F# types. Other .NET languages often use null as a value much more frequently. Because of this, F# code that is exposing a vanilla .NET API should check parameters for null at the API boundary, and prevent these values from flowing deeper into the F# implementation code. The `isNull` function or pattern matching on the `null` pattern can be used. + +```fsharp +let checkNonNull argName (arg: obj) = + match arg with + | null -> nullArg argName + | _ -> () + +let checkNonNull` argName (arg: obj) = + if isNull arg then nullArg argName + else () +``` + +#### Avoid using tuples as return values + +Instead, prefer returning a named type holding the aggregate data, or using out parameters to return multiple values. Although tuples and struct tuples exist in .NET (including C# language support for struct tuples), they will most often not provide the ideal and expected API for .NET developers. + +#### Avoid the use of currying of parameters + +Instead, use .NET calling conventions ``Method(arg1,arg2,…,argN)``. + +```fsharp +member this.TupledArguments(str, num) = String.replicate num str +``` + +Tip: If you’re designing libraries for use from any .NET language, then there’s no substitute for actually doing some experimental C# and Visual Basic programming to ensure that your libraries "feel right" from these languages. You can also use tools such as .NET Reflector and the Visual Studio Object Browser to ensure that libraries and their documentation appear as expected to developers. + +## Appendix + +### End-to-end example of designing F# code for use by other .NET languages + +Consider the following class: + +```fsharp +open System + +type Point1(angle,radius) = + new() = Point1(angle=0.0, radius=0.0) + member x.Angle = angle + member x.Radius = radius + member x.Stretch(l) = Point1(angle=x.Angle, radius=x.Radius * l) + member x.Warp(f) = Point1(angle=f(x.Angle), radius=x.Radius) + static member Circle(n) = + [ for i in 1..n -> Point1(angle=2.0*Math.PI/float(n), radius=1.0) ] +``` + +The inferred F# type of this class is as follows: + +```fsharp +type Point1 = + new : unit -> Point1 + new : angle:double * radius:double -> Point1 + static member Circle : n:int -> Point1 list + member Stretch : l:double -> Point1 + member Warp : f:(double -> double) -> Point1 + member Angle : double + member Radius : double +``` + +Let’s take a look at how this F# type appears to a programmer using another .NET language. For example, the approximate C# “signature” is as follows: + +```csharp +// C# signature for the unadjusted Point1 class +public class Point1 +{ + public Point1(); + + public Point1(double angle, double radius); + + public static Microsoft.FSharp.Collections.List Circle(int count); + + public Point1 Stretch(double factor); + + public Point1 Warp(Microsoft.FSharp.Core.FastFunc transform); + + public double Angle { get; } + + public double Radius { get; } +} +``` + +There are some important points to notice about how F# represents constructs here. For example: + +* Metadata such as argument names has been preserved. + +* F# methods that take two arguments become C# methods that take two arguments. + +* Functions and lists become references to corresponding types in the F# library. + +The following code shows how to adjust this code to take these things into account. + +```fsharp +namespace SuperDuperFSharpLibrary.Types + +type RadialPoint(angle:double, radius:double) = + + /// Return a point at the origin + new() = RadialPoint(angle=0.0, radius=0.0) + + /// The angle to the point, from the x-axis + member x.Angle = angle + + /// The distance to the point, from the origin + member x.Radius = radius + + /// Return a new point, with radius multiplied by the given factor + member x.Stretch(factor) = + RadialPoint(angle=angle, radius=radius * factor) + + /// Return a new point, with angle transformed by the function + member x.Warp(transform:Func<_,_>) = + RadialPoint(angle=transform.Invoke angle, radius=radius) + + /// Return a sequence of points describing an approximate circle using + /// the given count of points + static member Circle(count) = + seq { for i in 1..count -> + RadialPoint(angle=2.0*Math.PI/float(count), radius=1.0) } +``` + +The inferred F# type of the code is as follows: + +```fsharp +type RadialPoint = + new : unit -> RadialPoint + new : angle:double * radius:double -> RadialPoint + static member Circle : count:int -> seq + member Stretch : factor:double -> RadialPoint + member Warp : transform:System.Func -> RadialPoint + member Angle : double + member Radius : double +``` + +The C# signature is now as follows: + +```csharp +public class RadialPoint +{ + public RadialPoint(); + + public RadialPoint(double angle, double radius); + + public static System.Collections.Generic.IEnumerable Circle(int count); + + public RadialPoint Stretch(double factor); + + public RadialPoint Warp(System.Func transform); + + public double Angle { get; } + + public double Radius { get; } +} +``` + +The fixes made to prepare this type for use as part of a vanilla .NET library are as follows: + +* Adjusted several names: `Point1`, `n`, `l`, and `f` became `RadialPoint`, `count`, `factor`, and `transform`, respectively. + +* Used a return type of `seq` instead of `RadialPoint list` by changing a list construction using `[ ... ]` to a sequence construction using `IEnumerable`. + +* Used the .NET delegate type `System.Func` instead of an F# function type. + +This makes it far nicer to consume in C# code. diff --git a/docs/fsharp/style-guide/conventions.md b/docs/fsharp/style-guide/conventions.md new file mode 100644 index 0000000000000..f85d32bd5bd6c --- /dev/null +++ b/docs/fsharp/style-guide/conventions.md @@ -0,0 +1,642 @@ +--- +title: F# coding conventions +description: Learn general guidelines and idioms when writing F# code. +ms.date: 05/14/2018 +--- +# F# coding conventions + +The following conventions are formulated from experience working with large F# codebases. The [Five principles of good F# code](index.md#five-principles-of-good-f-code) are the foundation of each recommendation. They are related to the [F# component design guidelines](component-design-guidelines.md), but are applicable for any F# code, not just components such as libraries. + +## Organizing code + +F# features two primary ways to organize code: modules and namespaces. These are similar, but do have the following differences: + +* Namespaces are compiled as .NET namespaces. Modules are compiled as static classes. +* Namespaces are always top level. Modules can be top-level and nested within other modules. +* Namespaces can span multiple files. Modules cannot. +* Modules can be decorated with `[]` and `[]`. + +The following guidelines will help you use these to organize your code. + +### Prefer namespaces at the top level + +For any publicly consumable code, namespaces are preferential to modules at the top level. Because they are compiled as .NET namespaces, they are consumable from C# with no issue. + +```fsharp +// Good! +namespace MyCode + +type MyClass() = + ... +``` + +Using a top-level module may not appear different when called only from F#, but for C# consumers, callers may be surprised by having to qualify `MyClass` with the `MyCode` module. + +```fsharp +// Bad! +module MyCode + +type MyClass() = + ... +``` + +### Carefully apply `[]` + +The `[]` construct can pollute the scope of what is available to callers, and the answer to where something comes from is "magic". This is generally not a good thing. An exception to this rule is the F# Core Library itself (though this fact is also a bit controversial). + +However, it is a convenience if you have helper functionality for a public API that you wish to organize separately from that public API. + +```fsharp +module MyAPI = + [] + module private Helpers = + let helper1 x y z = + ... + + + let myFunction1 x = + let y = ... + let z = ... + + helper1 x y z +``` + +This lets you cleanly separate implementation details from the public API of a function without having to fully qualify a helper each time you call it. + +Additionally, exposing extension methods and expression builders at the namespace level can be neatly expressed with `[]`. + +### Use `[]` whenever names could conflict or you feel it helps with readability + +Adding the `[]` attribute to a module indicates that the module may not be opened and that references to the elements of the module require explicit qualified access. For example, the `Microsoft.FSharp.Collections.List` module has this attribute. + +This is useful when functions and values in the module have names that are likely to conflict with names in other modules. Requiring qualified access can greatly increase the long-term maintainability and evolvability of a library. + +```fsharp +[] +module StringTokenization = + let parse s = ... + +... + +let s = getAString() +let parsed = StringTokenization.parse s // Must qualify to use 'parse' +``` + +### Sort `open` statements topologically + +In F#, the order of declarations matters, including with `open` statements. This is unlike C#, where the effect of `using` and `using static` is independent of the ordering of those statements in a file. + +In F#, because elements opened into a scope can shadow others already present. This means that reordering `open` statements can alter the meaning of code. As a result, sorting alphanumerically (or pseudorandomly) is generally not recommended, lest you generate different behavior that you might expect. + +Instead, we recommend that you sort them [topologically](https://en.wikipedia.org/wiki/Topological_sorting); that is, order your `open` statements in the order in which _layers_ of your system are defined. Doing alphanumeric sorting within different topological layers may also be considered. + +As an example, here is the topological sorting for the F# compiler service public API file: + +```fsharp +namespace Microsoft.FSharp.Compiler.SourceCodeServices + +open System +open System.Collections.Generic +open System.Collections.Concurrent +open System.Diagnostics +open System.IO +open System.Reflection +open System.Text + +open Microsoft.FSharp.Core.Printf +open Microsoft.FSharp.Compiler +open Microsoft.FSharp.Compiler.AbstractIL +open Microsoft.FSharp.Compiler.AbstractIL.IL +open Microsoft.FSharp.Compiler.AbstractIL.ILBinaryReader +open Microsoft.FSharp.Compiler.AbstractIL.Diagnostics +open Microsoft.FSharp.Compiler.AbstractIL.Internal +open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library + +open Microsoft.FSharp.Compiler.AccessibilityLogic +open Microsoft.FSharp.Compiler.Ast +open Microsoft.FSharp.Compiler.CompileOps +open Microsoft.FSharp.Compiler.CompileOptions +open Microsoft.FSharp.Compiler.Driver +open Microsoft.FSharp.Compiler.ErrorLogger +open Microsoft.FSharp.Compiler.Lib +open Microsoft.FSharp.Compiler.PrettyNaming +open Microsoft.FSharp.Compiler.Parser +open Microsoft.FSharp.Compiler.Range +open Microsoft.FSharp.Compiler.Lexhelp +open Microsoft.FSharp.Compiler.Layout +open Microsoft.FSharp.Compiler.Tast +open Microsoft.FSharp.Compiler.Tastops +open Microsoft.FSharp.Compiler.TcGlobals +open Microsoft.FSharp.Compiler.Infos +open Microsoft.FSharp.Compiler.InfoReader +open Microsoft.FSharp.Compiler.NameResolution +open Microsoft.FSharp.Compiler.TypeChecker +open Microsoft.FSharp.Compiler.SourceCodeServices.SymbolHelpers + +open Internal.Utilities +open Internal.Utilities.Collections +open Microsoft.FSharp.Compiler.Layout.TaggedTextOps +``` + +Note that a line break separates topological layers, with each layer being sorted alphanumerically afterwards. This cleanly organizes code without accidentally shadowing values. + +## Use classes to contain values that have side effects + +There are many times when initializing a value can have side effects, such as instantiating a context to a database or other remote resource. It is tempting to initialize such things in a module and use it in subsequent functions: + +```fsharp +// This is bad! +module MyApi = + let dep1 = File.ReadAllText "/Users/{your name}/connectionstring.txt" + let dep2 = Environment.GetEnvironmentVariable "DEP_2" + let dep3 = Random().Next() // Random is not thread-safe + + let function1 arg = doStuffWith dep1 dep2 dep3 arg + let function2 arg = doSutffWith dep1 dep2 dep3 arg +``` + +This is frequently a bad idea for a few reasons: + +First, it makes the API itself reliant on shared state. For example, multiple calling threads may be attempting to access the `dep3` value (and it is not thread-safe). Secondly, it pushes application configuration into the codebase itself. This is difficult to maintain for larger codebases. + +Finally, module initialization compiles into a static constructor for the entire compilation unit. If any error occurs in let-bound value initialization in that module, it manifests as a `TypeInitializationException` that is then cached for the entire lifetime of the application. This can be difficult to diagnose. There is usually an inner exception that you can attempt to reason about, but if there is not, then there is no telling what the root cause is. + +Instead, just use a simple class to hold dependencies: + +```fsharp +type MyParametricApi(dep1, dep2, dep3) = + member __.Function1 arg1 = doStuffWith dep1 dep2 dep3 arg1 + member __.Function2 arg2 = doStuffWith dep1 dep2 dep3 arg2 +``` + +This enables the following: + +1. Pushing any dependent state outside of the API itself. +2. Configuration can now be done outside of the API. +3. Errors in initialization for dependent values are not likely to manifest as a `TypeInitializationException`. +4. The API is now easier to test. + +## Error management + +Error management in large systems is a complex and nuanced endeavor, and there are no silver bullets in ensuring your systems are fault-tolerant and behave well. The following guidelines should offer guidance in navigating this difficult space. + +### Represent error cases and illegal state in types intrinsic to your domain + +With [Discriminated Unions](../language-reference/discriminated-unions.md), F# gives you the ability to represent faulty program state in your type system. For example: + +```fsharp +type MoneyWithdrawalResult = + | Success of amount:decimal + | InsufficientFunds of balance:decimal + | CardExpired of DateTime + | UndisclosedFailure +``` + +In this case, there are three known ways that withdrawing money from a bank account can fail. Each error case is represented in the type, and can thus be dealt with safely throughout the program. + +```fsharp +let handleWithdrawal amount = + let w = withdrawMoney amount + match w with + | Success am -> printfn "Successfully withdrew %f" am + | InsufficientFunds balance -> printfn "Failed: balance is %f" balance + | CardExpired expiredDate -> printfn "Failed: card expired on %O" expiredDate + | UndisclosedFailure -> printfn "Failed: unknown" +``` + +In general, if you can model the different ways that something can **fail** in your domain, then error handling code is no longer treated as something you must deal with in addition to regular program flow. It is simply a part of normal program flow, and not considered **exceptional**. There are two primary benefits to this: + +1. It is easier to maintain as your domain changes over time. +2. Error cases are easier to unit test. + +### Use exceptions when errors cannot be represented with types + +Not all errors can be represented in a problem domain. These kinds of faults are *exceptional* in nature, hence the ability to raise and catch exceptions in F#. + +First, it is recommended that you read the [Exception Design Guidelines](../../standard/design-guidelines/exceptions.md). These are also applicable to F#. + +The main constructs available in F# for the purposes of raising exceptions should be considered in the following order of preference: + +| Function | Syntax | Purpose | +|----------|--------|---------| +| `nullArg` | `nullArg "argumentName"` | Raises a `System.ArgumentNullException` with the specified argument name. | +| `invalidArg` | `invalidArg "argumentName" "message"` | Raises a `System.ArgumentException` with a specified argument name and message. | +| `invalidOp` | `invalidOp "message"` | Raises a `System.InvalidOperationException` with the specified message. | +|`raise`| `raise (ExceptionType("message"))` | General-purpose mechanism for throwing exceptions. | +| `failwith` | `failwith "message"` | Raises a `System.Exception` with the specified message. | +| `failwithf` | `failwithf "format string" argForFormatString` | Raises a `System.Exception` with a message determined by the format string and its inputs. | + +Use `nullArg`, `invalidArg` and `invalidOp` as the mechanism to throw `ArgumentNullException`, `ArgumentException` and `InvalidOperationException` when appropriate. + +The `failwith` and `failwithf` functions should generally be avoided because they raise the base `Exception` type, not a specific exception. As per the [Exception Design Guidelines](../../standard/design-guidelines/exceptions.md), you want to raise more specific exceptions when you can. + +### Using exception-handling syntax + +F# supports exception patterns via the `try...with` syntax: + +```fsharp +try + tryGetFileContents() +with +| :? System.IO.FileNotFoundException as e -> // Do something with it here +| :? System.Security.SecurityException as e -> // Do something with it here +``` + +Reconciling functionality to perform in the face of an exception with pattern matching can be a bit tricky if you wish to keep the code clean. One such way to handle this is to use [active patterns](../language-reference/active-patterns.md) as a means to group functionality surrounding an error case with an exception itself. For example, you may be consuming an API that, when it throws an exception, encloses valuable information in the exception metadata. Unwrapping a useful value in the body of the captured exception inside the Active Pattern and returning that value can be helpful in some situations. + +### Do not use monadic error handling to replace exceptions + +Exceptions are seen as somewhat taboo in functional programming. Indeed, exceptions violate purity, so it's safe to consider them not-quite functional. However, this ignores the reality of where code must run, and that runtime errors can occur. In general, write code on the assumption that most things are neither pure nor total, to minimize unpleasant surprises. + +It is important to consider the following core strengths/aspects of Exceptions with respect to their relevance and appropriateness in the .NET runtime and cross-language ecosystem as a whole: + +1. They contain detailed diagnostic information, which is very helpful when debugging an issue. +2. They are well-understood by the runtime and other .NET languages. +3. They can reduce significant boilerplate when compared with code that goes out of its way to *avoid* exceptions by implementing some subset of their semantics on an ad-hoc basis. + +This third point is critical. For nontrivial complex operations, failing to use exceptions can result in dealing with structures like this: + +```fsharp +Result, string list> +``` + +Which can easily lead to fragile code like pattern matching on "stringly-typed" errors: + +```fsharp +let result = doStuff() +match result with +| Ok r -> ... +| Error e -> + if e.Contains "Error string 1" then ... + elif e.Contains "Error string 2" then ... + else ... // Who knows? +``` + +Additionally, it can be tempting to swallow any exception in the desire for a "simple" function that returns a "nicer" type: + +```fsharp +// This is bad! +let tryReadAllText (path : string) = + try System.IO.File.ReadAllText path |> Some + with _ -> None +``` + +Unfortunately, `tryReadAllText` can throw numerous exceptions based on the myriad of things that can happen on a file system, and this code discards away any information about what might actually be going wrong in your environment. If you replace this code with a result type, then you're back to "stringly-typed" error message parsing: + +```fsharp +// This is bad! +let tryReadAllText (path : string) = + try System.IO.File.ReadAllText path |> Ok + with e -> Error e.Message + +let r = tryReadAllText "path-to-file" +match r with +| Ok text -> ... +| Error e -> + if e.Contains "uh oh, here we go again..." then ... + else ... +``` + +And placing the exception object itself in the `Error` constructor just forces you to properly deal with the exception type at the call site rather than in the function. Doing this effectively creates checked exceptions, which are notoriously unfun to deal with as a caller of an API. + +A good alternative to the above examples is to catch *specific* exceptions and return a meaningful value in the context of that exception. If you modify the `tryReadAllText` function as follows, `None` has more meaning: + +```fsharp +let tryReadAllTextIfPresent (path : string) = + try System.IO.File.ReadAllText path |> Some + with :? FileNotFoundException -> None +``` + +Instead of functioning as a catch-all, this function will now properly handle the case when a file was not found and assign that meaning to a return. This return value can map to that error case, while not discarding any contextual information or forcing callers to deal with a case that may not be relevant at that point in the code. + +Types such as `Result<'Success, 'Error>` are appropriate for basic operations where they aren't nested, and F# optional types are perfect for representing when something could either return *something* or *nothing*. They are not a replacement for exceptions, though, and should not be used in an attempt to replace exceptions. Rather, they should be applied judiciously to address specific aspects of exception and error management policy in targeted ways. + +## Partial application and point-free programming + +F# supports partial application, and thus, various ways to program in a point-free style. This can be beneficial for code reuse within a module or the implementation of something, but it is generally not something to expose publicly. In general, point-free programming is not a virtue in and of itself, and can add a significant cognitive barrier for people who are not immersed in the style. Point-free programming in F# is basic for a well-trained mathematician, but can be difficult for people who are not familiar with lambda calculus. + +### Do not use partial application and currying in public APIs + +With little exception, the use of partial application in public APIs can be confusing for consumers. Usually, `let`-bound values in F# code are **values**, not **function values**. Mixing together values and function values can result in saving a small number of lines of code in exchange for quite a bit of cognitive overhead, especially if combined with operators such as `>>` to compose functions. + +### Consider the tooling implications for point-free programming + +Curried functions do not label their arguments. This has tooling implications. Consider the following two functions: + +```fsharp +let func name age = + printfn "My name is %s and I am %d years old!" name age + +let funcWithApplication = + printfn "My name is %s and I am %d years old!" +``` + +Both are valid functions, but `funcWithApplication` is a curried function. When you hover over their types in an editor, you see this: + +```fsharp +val func : name:string -> age:int -> unit + +val funcWithApplication : (string -> int -> unit) +``` + +At the call site, tooltips in tooling such as Visual Studio will not give you meaningful information as to what the `string` and `int` input types actually represent. + +If you encounter point-free code like `funcWithApplication` that is publicly consumable, it is recommended to do a full η-expansion so that tooling can pick up on meaningful names for arguments. + +Furthermore, debugging point-free code can be challenging, if not impossible. Debugging tools rely on values bound to names (for example, `let` bindings) so that you can inspect intermediate values midway through execution. When your code has no values to inspect, there is nothing to debug. In the future, debugging tools may evolve to synthesize these values based on previously executed paths, but it's not a good idea to hedge your bets on *potential* debugging functionality. + +### Consider partial application as a technique to reduce internal boilerplate + +In contrast to the previous point, partial application is a wonderful tool for reducing boilerplate inside of an application or the deeper internals of an API. It can be helpful for unit testing the implementation of more complicated APIs, where boilerplate is often a pain to deal with. For example, the following code shows how you can accomplish what most mocking frameworks give you without taking an external dependency on such a framework and having to learn a related bespoke API. + +For example, consider the following solution topography: + +``` +MySolution.sln +|_/ImplementationLogic.fsproj +|_/ImplementationLogic.Tests.fsproj +|_/API.fsproj +``` + +`ImplementationLogic.fsproj` might expose code such as: + +```fsharp +module Transactions = + let doTransaction txnContext txnType balance = + ... + +type Transactor(ctx, currentBalance) = + member __.ExecuteTransaction(txnType) = + Transactions.doTransaction ctx txtType currentBalance + ... +``` + +Unit testing `Transactions.doTransaction` in `ImplementationLogic.Tests.fspoj` is easy: + +```fsharp +namespace TransactionsTestingUtil + +open Transactions + +module TransactionsTestable = + let getTestableTransactionRoutine mockContext = Transactions.doTransaction mockContext +``` + +Partially applying `doTransaction` with a mocking context object lets you call the function in all of your unit tests without needing to construct a mocked context each time: + +```fsharp +namespace TransactionTests + +open Xunit +open TransactionTypes +open TransactionsTestingUtil +open TransactionsTestingUtil.TransactionsTestable + +let testableContext = + { new ITransactionContext with + member __.TheFirstMember() = ... + member __.TheSecondMember() = ... } + +let transactionRoutine = getTestableTransactionRoutine testableContext + +[] +let ``Test withdrawal transaction with 0.0 for balance``() = + let expected = ... + let actual = transactionRoutine TransactionType.Withdraw 0.0 + Assert.Equal(expected, actual) +``` + +This technique should not be universally applied to your entire codebase, but it is a good way to reduce boilerplate for complicated internals and unit testing those internals. + +## Access control + +F# has multiple options for [Access control](../language-reference/access-control.md), inherited from what is available in the .NET runtime. These are not just usable for types - you can use them for functions, too. + +* Prefer non-`public` types and members until you need them to be publicly consumable. This also minimizes what consumers couple to +* Strive to keep all helper functionality `private`. +* Consider the use of `[]` on a private module of helper functions if they become numerous. + +## Type inference and generics + +Type inference can save you from typing a lot of boilerplate. And automatic generalization in the F# compiler can help you write more generic code with almost no extra effort on your part. However, these features are not universally good. + +* Consider labeling argument names with explicit types in public APIs and do not rely on type inference for this. + + The reason for this is that **you** should be in control of the shape of your API, not the compiler. Although the compiler can do a fine job at inferring types for you, it is possible to have the shape of your API change if the internals it relies on have changed types. This may be what you want, but it will almost certainly result in a breaking API change that downstream consumers will then have to deal with. Instead, if you explicitly control the shape of your public API, then you can control these breaking changes. In DDD terms, this can be thought of as an Anti-corruption layer. + +* Consider giving a meaningful name to your generic arguments. + + Unless you are writing truly generic code that is not specific to a particular domain, a meaningful name can help other programmers understanding the domain they're working in. For example, a type parameter named `'Document` in the context of interacting with a document database makes it clearer that generic document types can be accepted by the function or member you are working with. + +* Consider naming generic type parameters with PascalCase. + + This is the general way to do things in .NET, so it's recommended that you use PascalCase rather than snake_case or camelCase. + +Finally, automatic generalization is not always a boon for people who are new to F# or a large codebase. There is cognitive overhead in using components that are generic. Furthermore, if automatically generalized functions are not used with different input types (let alone if they are intended to be used as such), then there is no real benefit to them being generic at that point in time. Always consider if the code you are writing will actually benefit from being generic. + +## Performance + +F# values are immutable by default, which allows you to avoid certain classes of bugs (especially those involving concurrency and parallelism). However, in certain cases, in order to achieve optimal (or even reasonable) efficiency of execution time or memory allocations, a span of work may best be implemented by using in-place mutation of state. This is possible in an opt-in basis with F# with the `mutable` keyword. + +However, use of `mutable` in F# may feel at odds with functional purity. This is fine, if you adjust expectations from purity to [referential transparency](https://en.wikipedia.org/wiki/Referential_transparency). Referential transparency - not purity - is the end goal when writing F# functions. This allows you to write a functional interface over a mutation-based implementation for performance critical code. + +### Wrap mutable code in immutable interfaces + +With referential transparency as a goal, it is critical to write code that does not expose the mutable underbelly of performance-critical functions. For example, the following code implements the `Array.contains` function in the F# core library: + +```fsharp +[] +let inline contains value (array:'T[]) = + checkNonNull "array" array + let mutable state = false + let mutable i = 0 + while not state && i < array.Length do + state <- value = array.[i] + i <- i + 1 + state +``` + +Calling this function multiple times does not change the underlying array, nor does it require you to maintain any mutable state in consuming it. It is referentially transparent, even though almost every line of code within it uses mutation. + +### Consider encapsulating mutable data in classes + +The previous example used a single function to encapsulate operations using mutable data. This is not always sufficient for more complex sets of data. Consider the following sets of functions: + +```fsharp +open System.Collections.Generic + +let addToClosureTable (key, value) (t: Dictionary<_,_>) = + if not (t.ContainsKey(key)) then + t.Add(key, value) + else + t.[key] <- value + +let closureTableCount (t: Dictionary<_,_>) = t.Count + +let closureTableContains (key, value) (t: Dictionary<_, HashSet<_>>) = + match t.TryGetValue(key) with + | (true, v) -> v.Equals(value) + | (false, _) -> false +``` + +This code is performant, but it exposes the mutation-based data structure that callers are responsible for maintaining. This can be wrapped inside of a class with no underlying members that can change: + +```fsharp +open System.Collections.Generic + +/// The results of computing the LALR(1) closure of an LR(0) kernel +type Closure1Table() = + let t = Dictionary>() + + member __.Add(key, value) = + if not (t.ContainsKey(key)) then + t.Add(key, value) + else + t.[key] <- value + + member __.Count = t.Count + + member __.Contains(key, value) = + match t.TryGetValue(key) with + | (true, v) -> v.Equals(value) + | (false, _) -> false +``` + +`Closure1Table` encapsulates the underlying mutation-based data structure, thereby not forcing callers to maintain the underlying data structure. Classes are a powerful way to encapsulate data and routines that are mutation-based without exposing the details to callers. + +### Prefer `let mutable` to reference cells + +Reference cells are a way to represent the reference to a value rather than the value itself. Although they can be used for performance-critical code, they are generally not recommended. Consider the following example: + +```fsharp +let kernels = + let acc = ref Set.empty + + processWorkList startKernels (fun kernel -> + if not ((!acc).Contains(kernel)) then + acc := (!acc).Add(kernel) + ...) + + !acc |> Seq.toList +``` + +The use of a reference cell now "pollutes" all subsequent code with having to dereference and re-reference the underlying data. Instead, consider `let mutable`: + +```fsharp +let kernels = + let mutable acc = Set.empty + + processWorkList startKernels (fun kernel -> + if not (acc.Contains(kernel)) then + acc <- acc.Add(kernel) + ...) + + acc |> Seq.toList +``` + +Aside from the single point of mutation in the middle of the lambda expression, all other code that touches `acc` can do so in a manner that is no different to the usage of a normal `let`-bound immutable value. This will make it easier to change over time. + +## Object programming + +F# has full support for objects and object-oriented (OO) concepts. Although many OO concepts are powerful and useful, not all of them are ideal to use. The following lists offer guidance on categories of OO features at a high level. + +**Consider using these features in many situations:** + +* Dot notation (`x.Length`) +* Instance members +* Implicit constructors +* Static members +* Indexer notation (`arr.[x]`) +* Named and Optional arguments +* Interfaces and interface implementations + +**Don't reach for these features first, but do judiciously apply them when they are convenient to solve a problem:** + +* Method overloading +* Encapsulated mutable data +* Operators on types +* Auto properties +* Implementing `IDisposable` and `IEnumerable` +* Type extensions +* Events +* Structs +* Delegates +* Enums + +**Generally avoid these features unless you must use them:** + +* Inheritance-based type hierarchies and implementation inheritance +* Nulls and `Unchecked.defaultof<_>` + +### Prefer composition over inheritance + +[Composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance) is a long-standing idiom that good F# code can adhere to. The fundamental principle is that you should not expose a base class and force callers to inherit from that base class to get functionality. + +### Use object expressions to implement interfaces if you don't need a class + +[Object Expressions](../language-reference/object-expressions.md) allow you to implement interfaces on the fly, binding the implemented interface to a value without needing to do so inside of a class. This is convenient, especially if you _only_ need to implement the interface and have no need for a full class. + +For example, here is the code that is run in [Ionide](http://ionide.io/) to provide a code fix action if you've added a symbol that you don't have an `open` statement for: + +```fsharp + let private createProvider () = + { new CodeActionProvider with + member this.provideCodeActions(doc, range, context, ct) = + let diagnostics = context.diagnostics + let diagnostic = diagnostics |> Seq.tryFind (fun d -> d.message.Contains "Unused open statement") + let res = + match diagnostic with + | None -> [||] + | Some d -> + let line = doc.lineAt d.range.start.line + let cmd = createEmpty + cmd.title <- "Remove unused open" + cmd.command <- "fsharp.unusedOpenFix" + cmd.arguments <- Some ([| doc |> unbox; line.range |> unbox; |] |> ResizeArray) + [|cmd |] + res + |> ResizeArray + |> U2.Case1 + } +``` + +Because there is no need for a class when interacting with the Visual Studio Code API, Object Expressions are an ideal tool for this. They are also valuable for unit testing, when you want to stub out an interface with test routines in an ad hoc manner. + +## Type Abbreviations + +[Type Abbreviations](../language-reference/type-abbreviations.md) are a convenient way to assign a label to another type, such as a function signature or a more complex type. For example, the following alias assigns a label to what's needed to define a computation with [CNTK](https://www.microsoft.com/cognitive-toolkit/), a deep learning library: + +```fsharp +open CNTK + +// DeviceDescriptor, Variable, and Function all come from CNTK +type Computation = DeviceDescriptor -> Variable -> Function +``` + +The `Computation` name is a convenient way to denote any function that matches the signature it is aliasing. Using Type Abbreviations like this is convenient and allows for more succinct code. + +### Avoid using Type Abbreviations to represent your domain + +Although Type Abbreviations are convenient for giving a name to function signatures, they can be confusing when abbreviating other types. Consider this abbreviation: + +```fsharp +// Does not actually abstract integers. +type BufferSize = int +``` + +This can be confusing in multiple ways: + +* `BufferSize` is not an abstraction; it is just another name for an integer. +* If `BufferSize` is exposed in a public API, it can easily be misinterpreted to mean more than just `int`. Generally, domain types have multiple attributes to them and are not primitive types like `int`. This abbreviation violates that assumption. +* The casing of `BufferSize` (PascalCase) implies that this type holds more data. +* This alias does not offer increased clarity compared with providing a named argument to a function. +* The abbreviation will not manifest in compiled IL; it is just an integer and this alias is a compile-time construct. + +```fsharp +module Networking = + ... + let send data (bufferSize: int) = + ... +``` + +In summary, the pitfall with Type Abbreviations is that they are **not** abstractions over the types they are abbreviating. In the previous example, `BufferSize` is just an `int` under the covers, with no additional data, nor any benefits from the type system besides what `int` already has. \ No newline at end of file diff --git a/docs/fsharp/style-guide/formatting.md b/docs/fsharp/style-guide/formatting.md new file mode 100644 index 0000000000000..62b2f2f8126b9 --- /dev/null +++ b/docs/fsharp/style-guide/formatting.md @@ -0,0 +1,517 @@ +--- +title: F# code formattin guidelines +description: Learn guidelines for formatting F# code. +ms.date: 05/14/2018 +--- +# F# code formatting guidelines + +This article offers guidelines for how to format your code so that your F# code is: + +* Generally viewed as more legible +* Is in accordance with conventions applied by formatting tools in Visual Studio and other editors +* Similar to other code online + +These guidelines are based on [A comprehensive guide to F# Formatting Conventions](https://github.com/dungpa/fantomas/blob/master/docs/FormattingConventions.md) by [Anh-Dung Phan](https://github.com/dungpa). + +## General rules for indentation + +F# uses significant whitespace by default. The following guidelines are intended to provide guidance as to how to juggle some challenges this can impose. + +### Using spaces + +When indentation is required, you must use spaces, not tabs. At least one space is required. Your organization can create coding standards to specify the number of spaces to use for indentation; two, three or four spaces of indentation at each level where indentation occurs is typical. + +**We recommend 4 spaces per indentation.** + +That said, indentation of programs is a subjective matter. Variations are OK, but the first rule you should follow is *consistency of indentation*. Choose a generally accepted style of indentation and use it systematically throughout your codebase. + +## Formatting discriminated union declarations + +Indent `|` in type definition by 4 spaces: + +```fsharp +// OK +type Volume = + | Liter of float + | FluidOunce of float + | ImperialPint of float + +// Not OK +type Volume = +| Liter of float +| USPint of float +| ImperialPint of float +``` + +Instantiated Discriminated Unions that split across multiple lines should give contained data a new scope with indentation: + +```fsharp +let tree1 = + BinaryNode + (BinaryNode(BinaryValue 1, BinaryValue 2), + BinaryNode(BinaryValue 3, BinaryValue 4)) +``` + +The closing parenthesis can also be on a new line: + +```fsharp +let tree1 = + BinaryNode( + BinaryNode(BinaryValue 1, BinaryValue 2), + BinaryNode(BinaryValue 3, BinaryValue 4) + ) +``` + +## Formatting tuples + +A tuple instantiation should be parenthesized, and the delimiting commas within should be followed by a single space, for example: `(1, 2)`, `(x, y, z)`. + +A commonly accepted exception is to omit parentheses in pattern matching of tuples: + +```fsharp +let (x, y) = z // Destructuring + +match x, y with +| 1, _ -> 0 +| x, 1 -> 0 +| x, y -> 1 +``` + +## Formatting records + +Short records can be written in one line: + +```fsharp +let point = { X = 1.0; Y = 0.0 } +``` + +Records that are longer should use new lines for labels: + +```fsharp +let rainbow = + { Boss = "Jeffrey" + Lackeys = ["Zippy"; "George"; "Bungle"] } +``` + +Placing the opening token on the same line and the closing token on a new line is also fine: + +```fsharp +let rainbow = { + Boss1 = "Jeffrey" + Boss2 = "Jeffrey" + Boss3 = "Jeffrey" + Boss4 = "Jeffrey" + Boss5 = "Jeffrey" + Boss6 = "Jeffrey" + Boss7 = "Jeffrey" + Boss8 = "Jeffrey" + Lackeys = ["Zippy"; "George"; "Bungle"] +} +``` + +The same rules apply for list and array elements. + +## Formatting lists and arrays + +Write `x :: l` with spaces around the `::` operator (`::` is an infix operator, hence surrounded by spaces) and `[1; 2; 3]` (`;` is a delimiter, hence followed by a space). + +Always use at least one space between two distinct brace-like operators. For example, leave a space between a `[` and a `{`. + +```fsharp +// OK +[ { IngredientName = "Green beans"; Quantity = 250 } + { IngredientName = "Pine nuts"; Quantity = 250 } + { IngredientName = "Feta cheese"; Quantity = 250 } + { IngredientName = "Olive oil"; Quantity = 10 } + { IngredientName = "Lemon"; Quantity = 1 } ] + +// Not OK +[{ IngredientName = "Green beans"; Quantity = 250 } + { IngredientName = "Pine nuts"; Quantity = 250 } + { IngredientName = "Feta cheese"; Quantity = 250 } + { IngredientName = "Olive oil"; Quantity = 10 } + { IngredientName = "Lemon"; Quantity = 1 }] +``` + +Lists and arrays that split across multiple lines follow a similar rule as records do: + +```fsharp +let pascalsTriangle = [| + [|1|] + [|1; 1|] + [|1; 2; 1|] + [|1; 3; 3; 1|] + [|1; 4; 6; 4; 1|] + [|1; 5; 10; 10; 5; 1|] + [|1; 6; 15; 20; 15; 6; 1|] + [|1; 7; 21; 35; 35; 21; 7; 1|] + [|1; 8; 28; 56; 70; 56; 28; 8; 1|] +|] +``` + +## Formatting if expressions + +Indentation of conditionals depends on the sizes of the expressions that make them up. If `cond`, `e1` and `e2` are small, simply write them on one line: + +```fsharp +if cond then e1 else e2 +``` + +If `e1` and `cond` are small, but `e2` is large: + +```fsharp +if cond then e1 +else + e2 +``` + +If `e1` and `cond` are large and `e2` is small: + +```fsharp +if cond then + e1 +else e2 +``` + +If all the expressions are large: + +```fsharp +if cond then + e1 +else + e2 +``` + +Multiple conditionals with `elif` and `else` are indented at the same scope as the `if`: + +```fsharp +if cond1 then e1 +elif cond2 then e2 +elif cond3 then e3 +else e4 +``` + +### Pattern matching constructs + +Use a `|` for each clause of a match with no indentation. If the expression is short, you can use a single line. + +```fsharp +// OK +match l with +| { him = x; her = "Posh" } :: tail -> _ +| _ :: tail -> findDavid tail +| [] -> failwith "Couldn't find David" + +// Not OK +match l with + | { him = x; her = "Posh" } :: tail -> _ + | _ :: tail -> findDavid tail + | [] -> failwith "Couldn't find David" + +// OK +match l with [] -> false | _ :: _ -> true +``` + +If the expression on the right of the pattern matching arrow is too large, move it to the following line, indented one step from the `match`/`|`. + +```fsharp +match lam with +| Var v -> 1 +| Abs(x, body) -> + 1 + sizeLambda body +| App(lam1, lam2) -> + sizeLambda lam1 + sizeLambda lam2 + +``` + +Pattern matching of anonymous functions, starting by `function`, should generally not indent too far. For example, indenting one scope as follows is fine: + +```fsharp +lambdaList +|> List.map (function + | Abs(x, body) -> 1 + sizeLambda 0 body + | App(lam1, lam2) -> sizeLambda (sizeLambda 0 lam1) lam2 + | Var v -> 1) +``` + +Pattern matching in functions defined by `let` or `let rec` should be indented 4 spaces after starting of `let`, even if `function` keyword is used: + +```fsharp +let rec sizeLambda acc = function + | Abs(x, body) -> sizeLambda (succ acc) body + | App(lam1, lam2) -> sizeLambda (sizeLambda acc lam1) lam2 + | Var v -> succ acc +``` + +We do not recommend aligning arrows. + +## Formatting try/with expressions + +Pattern matching on the exception type should be indented at the same level as `with`. + +```fsharp +try + if System.DateTime.Now.Second % 3 = 0 then + raise (new System.Exception()) + else + raise (new System.ApplicationException()) +with +| :? System.ApplicationException -> + printfn "A second that was not a multiple of 3" +| _ -> + printfn "A second that was a multiple of 3" +``` + +## Formatting function parameter application + +In general, most function parameter application is done on the same line. + +If you wish to apply parameters to a function on a new line, indent them by one scope. + +```fsharp +// OK +sprintf "\t%s - %i\n\r" + x.IngredientName x.Quantity + +// OK +sprintf + "\t%s - %i\n\r" + x.IngredientName x.Quantity + +// OK +let printVolumes x = + printf "Volume in liters = %f, in us pints = %f, in imperial = %f" + (convertVolumeToLiter x) + (convertVolumeUSPint x) + (convertVolumeImperialPint x) +``` + +Anonymous function arguments can be either on next line or with a dangling `fun` on the argument line: + +```fsharp +// OK +let printListWithOffset a list1 = + List.iter (fun elem -> + printfn "%d" (a + elem)) list1 + +// OK, but prefer previous +let printListWithOffset a list1 = + List.iter ( + fun elem -> + printfn "%d" (a + elem)) list1 +``` + +### Formatting infix operators + +Separate operators by spaces. Obvious exceptions to this rule are the `!` and `.` operators. + +Infix expressions are OK to lineup on same column: + +```fsharp +acc + +(sprintf "\t%s - %i\n\r" + x.IngredientName x.Quantity) + +let function1 arg1 arg2 arg3 arg4 = + arg1 + arg2 + + arg3 + arg4 +``` + +### Formatting pipeline operators + +Pipeline `|>` should go at the start of a line immediately under the expression being operated on: + +```fsharp +// OK +let methods2 = System.AppDomain.CurrentDomain.GetAssemblies() + |> List.ofArray + |> List.map (fun assm -> assm.GetTypes()) + |> Array.concat + |> List.ofArray + |> List.map (fun t -> t.GetMethods()) + |> Array.concat + +// OK, but prefer previous +let methods2 = + System.AppDomain.CurrentDomain.GetAssemblies() + |> List.ofArray + |> List.map (fun assm -> assm.GetTypes()) + |> Array.concat + |> List.ofArray + |> List.map (fun t -> t.GetMethods()) + |> Array.concat + +// Not OK +let methods2 = System.AppDomain.CurrentDomain.GetAssemblies() + |> List.ofArray + |> List.map (fun assm -> assm.GetTypes()) + |> Array.concat + |> List.ofArray + |> List.map (fun t -> t.GetMethods()) + |> Array.concat +``` + +### Formatting modules + +Code in a local module must be indented relative to the module, but code in a top-level module should not be indented. Namespace elements do not have to be indented. + +```fsharp +// A is a top-level module. +module A + +let function1 a b = a - b * b +``` + +```fsharp +// A1 and A2 are local modules. +module A1 = + let function1 a b = a*a + b*b + +module A2 = + let function2 a b = a*a - b*b +``` + +### Formatting object expressions and interfaces + +Object expressions and interfaces should be aligned in the same way with `member` being indented after 4 spaces. + +```fsharp +let comparer = + { new IComparer with + member x.Compare(s1, s2) = + let rev (s : String) = + new String (Array.rev (s.ToCharArray())) + let reversed = rev s1 + reversed.CompareTo (rev s2) } +``` + +### Formatting whitespace in expressions + +Avoid extraneous whitespace in F# expressions. + +```fsharp +// OK +spam (ham.[1]) + +// Not OK +spam ( ham.[ 1 ] ) +``` + +Named arguments should also not have space surrounding the `=`: + +```fsharp +// OK +let makeStreamReader x = new System.IO.StreamReader(path=x) + +// Not OK +let makeStreamReader x = new System.IO.StreamReader(path = x) +``` + +## Formatting blank lines + +* Separate top-level function and class definitions with two blank lines. +* Method definitions inside a class are separated by a single blank line. +* Extra blank lines may be used (sparingly) to separate groups of related functions. Blank lines may be omitted between a bunch of related one-liners (for example, a set of dummy implementations). +* Use blank lines in functions, sparingly, to indicate logical sections. + +## Formatting comments + +Generally prefer multiple double-slash comments over ML-style block comments. + +```fsharp +// Prefer this style of comments when you want +// to express written ideas on multiple lines. + +(* + Generally avoid these kinds of comments. +*) +``` + +Inline comments should capitalize the first letter. + +```fsharp +let f x = x + 1 // Increment by one. +``` + +## Naming conventions + +### Use camelCase for class-bound, expression-bound and pattern-bound values and functions + +It is common and accepted F# style to use camelCase for all names bound as local variables or in pattern matches and function definitions. + +```fsharp +// OK +let addIAndJ i j = i + j + +// Bad +let addIAndJ I J = I+J + +// Bad +let AddIAndJ i j = i + j +``` + +Locally-bound functions in classes should also use camelCase. + +```fsharp +type MyClass() = + + let doSomething () = + + let firstResult = ... + + let secondResult = ... + + member x.Result = doSomething() +``` + +### Use camelCase for internal and private module-bound values and functions + +Use camelCase for private module-bound values, including the following: + +* Ad hoc functions in scripts + +* Values making up the internal implementation of a module or type + +```fsharp +let emailMyBossTheLatestResults = + ... +``` + +### Avoid underscores in names + +Historically, some F# libraries have used underscores in names. However, this is no longer widely accepted, partly because it clashes with .NET naming conventions. That said, some F# programmers use underscores heavily, partly for historical reasons, and tolerance and respect is important. However, be aware that the style is often disliked by others who have a choice about whether to use it. + +Some exceptions includes interoperating with native components, where underscores are very common. + +### Use standard F# operators + +The following operators are defined in the F# standard library and should be used instead of defining equivalents. Using these operators is recommended as it tends to make code more readable and idiomatic. Developers with a background in OCaml or other functional programming language may be accustomed to different idioms. The following list summarizes the recommended F# operators. + +```fsharp +x |> f // Forward pipeline +f >> g // Forward composition +x |> ignore // Throwing away a value +x + y // Overloaded addition (including string concatenation) +x - y // Overloaded subtraction +x * y // Overloaded multiplication +x / y // Overloaded division +x % y // Overloaded modulus +x && y // Lazy/short-cut "and" +x || y // Lazy/short-cut "or" +x <<< y // Bitwise left shift +x >>> y // Bitwise right shift +x ||| y // Bitwise or, also for working with “flags” enumeration +x &&& y // Bitwise and, also for working with “flags” enumeration +x ^^^ y // Bitwise xor, also for working with “flags” enumeration +``` + +### Use prefix syntax for generics (`Foo`) in preference to postfix syntax (`T Foo`) + +F# inherits both the postfix ML style of naming generic types (for example, `int list`) as well as the prefix .NET style (for example, `list`). Prefer the .NET style, except for four specific types: + +1. For F# Lists, use the postfix form: `int list` rather than `list`. +2. For F# Options, use the postfix form: `int option` rather than `option`. +3. For F# arrays, use the syntactic name `int[]` rather than `int array` or `array`. +4. For Reference Cells, use `int ref` rather than `ref` or `Ref`. + +For all other types, use the prefix form. \ No newline at end of file diff --git a/docs/fsharp/style-guide/index.md b/docs/fsharp/style-guide/index.md new file mode 100644 index 0000000000000..6c8c5cca1fa78 --- /dev/null +++ b/docs/fsharp/style-guide/index.md @@ -0,0 +1,42 @@ +--- +title: F# style guide +description: Learn the five principles of good F# code. +ms.date: 05/14/2018 +--- +# F# style guide + +The following articles describe guidelines for formatting F# code and topical guidance for features of the language and how they should be used. + +This guidance has been formulated based on the use of F# in large codebases with a diverse group of programmers. This guidance generally leads to successful use of F# and minimizes frustrations when requirements for programs change over time. + +## Five principles of good F# code + +You should keep the following principles in mind any time you write F# code, especially in systems that will change over time. Every piece of guidance in further articles stems from these five points. + +1. **Good F# code is succinct and expressive** + + F# has many features that allow you to express actions in fewer lines of code and reuse generic functionality. The F# core library also contains many useful types and functions for working with common collections of data. As a general rule, if you can express a solution to a problem in fewer lines of code, other developers (or your future self) will be appreciative. It is also highly recommended that you use a library such as FSharp.Core, the [vast .NET libraries](https://docs.microsoft.com/dotnet/api/) that F# runs on, or a third-party package on [NuGet](https://www.nuget.org/) when you need to do a nontrivial task. + +2. **Good F# code is interoperable** + + Interoperation can take multiple forms, including consuming code in different languages. The boundaries of your code that other callers interoperate with are critical pieces to get right. When writing F#, you should always be thinking about how other code will call into the code you are writing, including if they do so from another language like C#. The [F# Component Design Guidelines](component-design-guidelines.md) describe interoperability in detail. + +3. **Good F# code makes use of object programming, not object orientation** + + F# has full support for programming with objects in .NET, including [classes](../language-reference/classes.md), [interfaces](../language-reference/interfaces.md), [access modifiers](../language-reference/access-control.md), [abstract classes](../language-reference/abstract-classes.md), and so on. For more complicated functional code, such as functions that must be context-aware, objects can easily encapsulate contextual information in ways that functions cannot. Features such as [optional parameters](../language-reference/members/methods.md#optional-arguments) and [overloading](../language-reference/members/methods.md#overloaded-methods) also aid consumption of this functionality for callers. + +4. **Good F# code performs well without exposing mutation** + + It's no secret that to write high-performance code, you must use mutation. It's how computers work, after all. Such code is often error-prone and difficult to get right. Avoid exposing mutation to callers. Seek a functional interface over a mutation-based implementation. + +5. **Good F# code is toolable** + + Tools are invaluable for working in large codebases, you can write F# code such that it can be used more effectively with F# language tooling. One example is making sure you don't overdo it with a point-free style of programming, so that intermediate values can be inspected with a debugger. Another example is using [XML documentation comments](../language-reference/xml-documentation.md) describing constructs such that tooltips in editors can display those comments at the call site. Always think about how your code will be read, navigated, debugged, and manipulated by other programmers with their tools. + +## Next steps + +The [F# Formatting Guidelines](formatting.md) provide guidance on how to format code so that it is easy to read. + +The [F# coding conventions](conventions.md) provide guidance for F# programming idioms that will help the long-term maintenance of larger F# codebases. + +The [F# Component Design Guidelines](component-design-guidelines.md) is a comprehensive set of guidance for authoring F# components, such as libraries. \ No newline at end of file diff --git a/docs/toc.md b/docs/toc.md index e42d7144cfa9d..987783ff30916 100644 --- a/docs/toc.md +++ b/docs/toc.md @@ -285,7 +285,12 @@ ### [Get Started with Visual Studio for Mac](fsharp/get-started/get-started-with-visual-studio-for-mac.md) ### [Get Started with Visual Studio Code and Ionide](fsharp/get-started/get-started-vscode.md) ### [Get Started with with the .NET Core CLI](fsharp/get-started/get-started-command-line.md) - + +## [F# style guide](fsharp/style-guide/index.md) +### [F# formatting guidelines](fsharp/style-guide/formatting.md) +### [F# coding conventions](fsharp/style-guide/conventions.md) +### [F# component design guidelines](fsharp/style-guide/component-design-guidelines.md) + ## Tutorials ### [F# Interactive](fsharp/tutorials/fsharp-interactive/index.md) ### [Type Providers](fsharp/tutorials/type-providers/index.md)