F# code examples

functional design methodology and f sharp and c sharp and f# parallel programming
GregDeamons Profile Pic
GregDeamons,New Zealand,Professional
Published Date:03-08-2017
Your Website URL(Optional)
Comment
Syme_850-4C19.fm Page 545 Tuesday, October 16, 2007 2:53 PM CHA P TE R 1 9 ■ ■ ■ Designing F Libraries T his book deals with F, a language situated in the context of .NET-based software construc- tion and engineering. As an expert F programmer, you will need more than a knowledge of the F language; you will also need to use a range of software engineering tools and methodologies wisely to let you build software that is truly valuable for the situation where it is deployed. We touched on some important tools in the previous chapter. In this final chapter, we look at some of the methodological issues related to F library design. In particular: • We take a look at designing “vanilla” .NET libraries according to existing .NET design conventions and that minimize the use of F-specific constructs. � We briefly consider some of the elements of “functional programming design method- ology,” which offers important and deep insights into programming but doesn’t address several important aspects of the library or component design problems. � We give some specific suggestions on designing .NET and F libraries, including naming conventions, how to design types and modules, and guidelines for using exceptions. F is often seen as a functional language, but, as we have emphasized in this book, it is in reality a multiparadigm language; the OO, functional, imperative, and language-manipulation paradigms are all well supported. That is, F is a function-oriented language—many of the defaults are set up to encourage functional programming, but programming in the other paradigms is effective and efficient, and a combination is often best of all. A multiparadigm language brings challenges for library designs and coding conventions. It is a common misconception that the functional and object-oriented programming methodologies are competing; it fact, they are largely orthogonal. However, it is important to note that functional programming does not directly solve many of the practical and prosaic issues associated with practical library design—for solutions to these problems, we must turn elsewhere. In the context of .NET programming, this means turning first to the .NET Library Design Guidelines, published online by Microsoft and as a book by Addison-Wesley. In the official documents, the .NET library design is described in terms of conventions and guidelines for the use of the following constructs in public framework libraries: � Assemblies, namespaces, and types (see Chapters 6 and 7 in this book) � Classes and objects, containing properties, methods, and events (see Chapter 6) � Interfaces (in other words, object interface types; see Chapter 6) 545Syme_850-4C19.fm Page 546 Tuesday, October 16, 2007 2:53 PM 546 CHAPTER 19 ■ DESIGNING F LIBRARIES � .NET delegate types (mentioned briefly in Chapters 5 and 6) � Enumerations (that is, enums from languages such as C; mentioned briefly in Chapter 6) � Constants (that is, constant literals from languages such as C) � Type parameters (that is, generic parameters; see Chapter 5) From the perspective of F programming, you must also consider the following constructs: � Discriminated union types and their tags (Chapters 3 and 9) � Record types and their fields (Chapter 3) � Type abbreviations (Chapter 3) � Values and functions declared using let and let rec (Chapter 3) �Modules (Chapter 6) �Named arguments (Chapter 6) � Optional arguments (Chapter 6) Framework library design is always nontrivial and often underestimated. F framework and library design methodology is inevitably strongly rooted in the context of .NET object-oriented programming. In this chapter, we give our opinions on how you can go about approaching library design in the context of F programming. The opinions are neither proscriptive nor “official.” More official guidelines may be developed by the F team and community at some future point, though ultimately the final choices lie with F programmers and software architects. ■Note Some F programmers choose to use library and coding conventions much more closely associated with OCaml, with Python, or with a particular application domain such as hardware verification. For example, OCaml coding uses underscores in names extensively, a practice avoided by the .NET Framework guidelines but used in places by the F library itself. Some also choose to adjust coding conventions to their personal or team tastes. Designing Vanilla .NET Libraries One way to approach library design with F is to simply design libraries according to the .NET Library Design Guidelines. This implicitly can mean avoiding or minimizing the use of F-specific or F-related constructs in the public API. We will call these libraries vanilla .NET libraries, as opposed to libraries that use F constructs without restriction and are mostly intended for use by F applications. Designing vanilla .NET libraries means adopting the following rules:Syme_850-4C19.fm Page 547 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 547 � Apply the .NET Library Design Guidelines to the public API of your code. Your internal implementation can use any techniques you want. � Restrict the constructs you use in your public APIs to those that are most easily used and recognized by .NET programmers. This means avoiding the use of some F idioms in the public API. � Use the .NET quality assurance tool FxCop to check the public interface of your assembly for compliance. Use FxCop exemptions where you deem necessary. At the time of writing, here are some specific recommendations from the authors of this book: � Avoid using F list types 'a list in vanilla .NET APIs. Use seq'a or arrays instead of lists. � Avoid using F function types in vanilla .NET APIs. F function values tend to be a little difficult to create from other .NET languages. Instead consider using .NET delegate types such as the overloaded System.Func... types available from .NET 3.5 onward. � Avoid using F-specific language constructs such as discriminated unions and optional arguments in vanilla .NET APIs. For example, consider the code in Listing 19-1, which shows some F code that we intend to adjust to be suitable for use as part of a .NET API. Listing 19-1. An F Type Prior to Adjustment for Use as Part of a Vanilla .NET API open System type APoint(angle,radius) = member x.Angle = angle member x.Radius = radius member x.Stretch(l) = APoint(angle=x.Angle, radius=x.Radius l) member x.Warp(f) = APoint(angle=f(x.Angle), radius=x.Radius) static member Circle(n) = for i in 1..n - APoint(angle=2.0Math.PI/float(n), radius=1.0) new() = APoint(angle=0.0, radius=0.0) The inferred F type of this class is as follows: type APoint = new : unit - APoint new : angle:double radius:double - APoint static member Circle : n:int - APoint list member Stretch : l:double - APoint member Warp : f:(double - double) - APoint member Angle : double member Radius : doubleSyme_850-4C19.fm Page 548 Tuesday, October 16, 2007 2:53 PM 548 CHAPTER 19 ■ DESIGNING F LIBRARIES Let’s take a look at how this F type will appear to a programmer using C or another .NET library. The approximate C “signature” is as follows: // C signature for the unadjusted APoint class of Listing 19-1 public class APoint public APoint(); public APoint(double angle, double radius); public static Microsoft.FSharp.Collections.ListAPoint Circle(int count); public APoint Stretch(double factor); public APoint Warp(Microsoft.FSharp.Core.FastFuncdouble,double transform); public double Angle get; public double Radius get; There are some important points to notice about how F has chosen to represent 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 full rules for how F types, modules, and members are represented in the .NET Common Intermediary Language are explained in the F language reference on the F website. To make a .NET component, we place it in a file component.fs and compile this code into a strong-name signed DLL using the techniques from Chapter 7: C:\fsharp sn -k component.snk C:\fsharp fsc –a component.fs version 1.0.0.0 keyfile component.snk Figure 19-1 shows the results of applying the Microsoft FxCop tool to check this assembly for compliance with the .NET Framework Design Guidelines. Figure 19-1 reveals a number of problems with the assembly. For example, the .NET Framework Design Guidelines require the following: � Types must be placed in namespaces. � Public identifiers must be spelled correctly. � Additional attributes must be added to assemblies related to .NET Security and Common Language Specification (CLS) compliance.Syme_850-4C19.fm Page 549 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 549 Figure 19-1. Running FxCop on the code from Listing 19-1 Listing 19-2 shows how to adjust this code to take these things into account. Listing 19-2. An F Type After Adjustment for Use As Part of a Vanilla .NET API light namespace ExpertFSharp.Types open System open System.Security.Permissions assembly: SecurityPermission(SecurityAction.RequestMinimum, Execution = true); assembly: System.Runtime.InteropServices.ComVisible(false); assembly: System.CLSCompliant(true); assembly: PermissionSet(SecurityAction.RequestOptional, Name = "Nothing") type RadialPoint(angle,radius) = member x.Angle = angle member x.Radius = radius member x.Stretch(factor) = RadialPoint(angle=x.Angle, radius=x.Radius factor) member x.Warp(transform:Converter_,_) = RadialPoint(angle=transform.Invoke(x.Angle), radius=x.Radius) static member Circle(count) = seq for i in 1..count - RadialPoint(angle=2.0Math.PI/float(count), radius=1.0) new() = RadialPoint(angle=0.0, radius=0.0)Syme_850-4C19.fm Page 550 Tuesday, October 16, 2007 2:53 PM 550 CHAPTER 19 ■ DESIGNING F LIBRARIES The inferred F type of the code in Listing 19-2 is as follows: type RadialPoint = new : unit - RadialPoint new : angle:double radius:double - RadialPoint static member Circle : count:int - seqRadialPoint member Stretch : factor:double - RadialPoint member Warp : transform:System.Converterdouble,double - RadialPoint member Angle : double member Radius : double The C signature is now as follows: // C signature for the unadjusted APoint class of Listing 19-2 public class RadialPoint public RadialPoint(); public RadialPoint(double angle, double radius); public static System.Collections.Generic.IEnumerableRadialPoint Circle(int count); public RadialPoint Stretch(double factor); public RadialPoint Warp(System.Converterdouble,double transform); public double Angle get; public double Radius get; The fixes we have made to prepare this type for use as part of a vanilla .NET library are as follows: � We added several attributes as directed by the FxCop tool. You can find the meaning of these attributes in the MSDN documentation referenced by the FxCop warning messages. � We adjusted several names; APoint , n, l, and f became RadialPoint, count, factor, and transform, respectively. � We used a return type of seqRadialPoint instead of RadialPoint list by changing a list construction using ... to a sequence construction using seq ... . An alter- native option would be to use an explicit upcast ( ... : seq_). � We used the .NET delegate type System.Converter instead of an F function type. After applying these, the last remaining FxCop warning is simply telling us that namespaces with two to three types are not recommended. The last two previous points are not essential, but, as mentioned, delegate types and sequence types tend to be easier for C programmers to use than F function and list types (F function types are not compiled to .NET delegate types, partly for performance reasons). Note you can use FxCop exemptions to opt out of any of the FxCop rules, either by adding an exemption entry to FxCop itself or by attaching attributes to your source code. Syme_850-4C19.fm Page 551 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 551 ■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 uses of your libraries look good 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. If necessary, enroll the help of a C programmer and ask them what they think. Understanding Functional Design Methodology So far in this chapter you have looked at how to do “vanilla” .NET library design with F. However, frequently F programmers are designing libraries that are free to make more sophisticated use of F and more or less assume that client users are using F as well. To make the best use of F in this situation, it is helpful to use functional programming design techniques as part of the library design process. For this reason, we’ll next cover what functional programming brings to the table with regard to design methodology. Understanding Where Functional Programming Comes From Let’s recap the origins of the major programming paradigms from a design perspective: � Procedural programming arises from the fundamentally imperative nature of processing devices: microprocessors are state machines that manipulate data using side effects. � Object-oriented programming arises from the need to encapsulate and reuse large objects such as those used for GUI applications. � Functional programming differs in that it arises from one view of the mathematics of computation. That is, functional programming, in its purest form, is a way of describing computations using constructs that have useful mathematical properties, independent of their implementations. For example, functional programming design methodology places great importance on constructs that are compositional. Let’s take a simple example: in F, we can map a function over a list of lists as follows: open List let map2 f inp = map (map f) inp This is a simple example of the inherent compositionality of generic functions: the expression map f has produced a new function that can in turn be used as the argument to map. Understanding compositionality is the key to understanding much of what goes by the name of functional programming. For example, functional programmers aren’t interested in a lack of side effects “just for the sake of it”—instead, they like programs that don’t use side effects simply because they tend to be more compositional than those that do. Functional programming often goes further by emphasizing transformations that preserve behavior. For example, we expect to be able to make the following refactorings to our code regardless of the function f or of the values inp, x, or rest:Syme_850-4C19.fm Page 552 Tuesday, October 16, 2007 2:53 PM 552 CHAPTER 19 ■ DESIGNING F LIBRARIES open List hd (x :: rest) replaced by x concat (map (filter f) inp) replaced by filter f (concat inp) Equations like these can be a source of useful documentation and test cases, and in some situations they can even be used to specify whole programs. Furthermore, good programmers routinely manipulate and optimize programs in ways that effectively assume these transfor- mations are valid. Indeed, if these transformations are not valid, then it is easy to accidentally insert bugs when working with code. That said, many important transformation equations aren’t guaranteed to always be valid—they typically hold only if additional assumptions are made—for example, in the first example the expression rest should not have side effects. Transformational reasoning works well for some kinds of code and badly for others. Table 19-1 lists some of the F and .NET constructs that are highly compositional and for which transformational reasoning tends to work well in practice. Table 19-1. Some Compositional F Library Constructs Amenable to Equational Reasoning Constructs Examples Explanation Base types int, float Code using immutable basic types is often relatively easy to reason about. There are some exceptions to the rule: the presence of NaN values and approximations in floating-point operations can make it difficult to reason about floating- point code. Collections Set'a, Map'k,'v Immutable collection types are highly amenable to equational reasoning. For example, we expect equations such as Set.union Set.empty x = x to hold. Control types Lazy'a, Async'a Control constructs are often highly composi- tional and have operators that allow you to combine them in interesting ways. For example, we expect equations such as (lazy x).Force() = x to hold. Data abstractions seq'a F sequences are not “pure” values, because they may access data stores using major side effects such as network I/O. However, in practice, uses of sequences tend to be very amenable to equa- tional reasoning. This assumes that the side effects for each iteration of the sequence are isolated and independent. Understanding Functional Design Methodology Functional design methodology itself is thus rooted in compositionality and reasoning. In practice, it is largely about the following steps:Syme_850-4C19.fm Page 553 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 553 1. Deciding what values you’re interested in representing. These values may range from simple integers to more sophisticated objects such expression trees from Chapter 9 or the asynchronous tasks from Chapter 13. 2. Deciding what operations are required to build these values, extracting information from them, and combining them and transforming them. 3. Deciding what equations and other algebraic properties should hold between these values and assessing whether these properties hold the implementation. Steps 1 and 2 explain why functional programmers often prefer to define operations sepa- rately from types. As a result, functional programmers often find object-oriented programming strange because it emphasizes operations on single values, while functional programming emphasizes operations that combine values. This carries over to library implementation in functional programming, where you will often see types defined first and then modules containing operations on those types. Because of this, one pattern that is quite common in the F library is the following: � The type is defined first. � Then there is a module that defines the functions to work over the type. � Finally, there is a with augmentation that adds the most common functions as members. We described augmentations in Chapter 6. One simple example of functional programming methodology in this book is in Chapter 12, where you saw how a representation for propositional logic is defined using a type: type Var = string type Prop = And of Prop Prop Var of Var Not of Prop Exists of Var Prop False Operations were then defined to combine and analyze values of type Prop. It would not make sense to define all of these operations as intrinsic to the Prop type, an approach often taken in OO design. In that same chapter, you saw another representation of propositional logic formulae where two logically identical formulae were normalized to the same represen- tations. This is an example of step 3 of functional design methodology: the process of designing a type involves specifying the equations that should hold for values of that type. You’ve seen many examples in this book of how object-oriented programming and func- tional programming can work very well together. For example, F objects are often immutable but use OO features to group together some functionality working on the same data. Also, F object interface types are often used as a convenient notation for collections of functions. However, there are some tensions between functional programming and object-oriented design methodology. For example, when you define operations independently of data (that is, the functional style), it is simple to add a new operation, but modifying the type is more difficult. Syme_850-4C19.fm Page 554 Tuesday, October 16, 2007 2:53 PM 554 CHAPTER 19 ■ DESIGNING F LIBRARIES In object-oriented programming using abstract and virtual methods, it is easy to add a new inherited type, but adding new operations (that is, new virtual methods) is difficult. Similarly, functional programming emphasizes simple but compositional types, for example, functions and tuples. Object-oriented programming tends to involve creating many (often large and complex) types with considerable amounts of additional metadata. These are often less compositional but sometimes more self-documenting. Finally, we mention that although functional programming does not provide a complete software design methodology, it is beautiful and powerful when it works, creating constructs that can be wielded with amazing expressivity and a very low bug rate. However, not all constructs in software design are amenable to compositional descriptions and implementations, and an over-reliance on “pure” programming can leave the programmer bewildered and abandoned when the paradigm doesn’t offer useful solutions that scale in practice. This is the primary reason why F is a multiparadigm language: to ensure that functional techniques can be combined with other techniques where appropriate. ■Note Some functional languages such as Haskell place strong emphasis on the equational reasoning principles. In F, equational reasoning is slightly less important; however, it still forms an essential part of understanding what functional programming brings to the arena of design methodology. Applying the .NET Design Guidelines to F In this section, we will present some additional recommendations for applying the .NET Library Design Guidelines to F programming. We do this by making a series of recommendations that can be read as extensions to these guidelines. Recommendation: Use the .NET naming and capitalization conventions where possible. Table 19-2 summarizes the .NET guidelines for naming and capitalization in code. We have added our own recommendations for how these should be adjusted for some F constructs. This table refers to the following categories of names: � PascalCase: LeftButton and TopRight, for example � camelCase: leftButton and topRight, for example � Verb: A verb or verb phrase; performAction or SetValue, for example � Noun: A noun or noun phrase; cost or ValueAfterDepreciation, for example � Adjective: An adjective or adjectival phrase; Comparable or Disposable, for example In general, the .NET guidelines strongly discourage the use of abbreviations (for example, “use OnButtonClick rather than OnBtnClick”). Common abbreviations such as Async for Asynchronous are tolerated. This guideline has historically been broken by 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 programming, though we discourage using additional abbreviations beyond those already found in existing F libraries. Syme_850-4C19.fm Page 555 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 555 Acronyms such as XML are not abbreviations and are widely used in .NET libraries, though in uncapitalized form (Xml). Only well-known, widely recognized acronyms should be used. The .NET guidelines say that casing cannot be used to avoid name collisions and that you must assume that some client languages are case insensitive. For example, Visual Basic is case insensitive. Table 19-2. Conventions Associated with Public Constructs in .NET Frameworks and Author-Recommended Extensions for F Constructs Construct Case Part Examples Notes Concrete PascalCase Noun/ List, DoubleComplex Concrete types are structs, types adjective classes, enumerations, delegates, records, and unions. Type names are tradition- ally lowercase in OCaml, and F code has generally followed this pattern. However, as F matures as a language, it is moving much more to follow standardized .NET idioms. DLLs PascalCase Microsoft.FSharp.Core.dll Company.Component.dll Union PascalCase Noun Some, Add, Success Do not use a prefix in public tags APIs. Optionally use a prefix when internal, such as type Teams = TAlpha TBeta TDelta. Event PascalCase Verb ValueChanged Exceptions PascalCase WebException Field PascalCase Noun CurrentName Interface PascalCase Noun/ IDisposable types adjective Method PascalCase Verb ToString Namespace PascalCase Microsoft.FSharp.Core Generally use Organization. Technology. Subnamespace, though drop the organization if the technology is indepen- dent of organization. Parameters camelCase Noun typeName, transform, range let values camelCase Noun/verb getValue, myTable (internal)Syme_850-4C19.fm Page 556 Tuesday, October 16, 2007 2:53 PM 556 CHAPTER 19 ■ DESIGNING F LIBRARIES Table 19-2. Conventions Associated with Public Constructs in .NET Frameworks and Author-Recommended Extensions for F Constructs (Continued) Construct Case Part Examples Notes let values camelCase or Noun List.map, Dates.Today let-bound values are often (external) PascalCase public when following traditional functional design patterns. However, generally use PascalCase when the identifier can be used from other .NET languages. Property PascalCase Noun/ IsEndOfFile, BackColor Boolean properties adjective generally use Is and Can and should be affirmative, as in IsEndOfFile, not IsNotEndOfFile. Type Any Noun/ 'a, 't, 'Key, 'Value parameters adjective We generally recommend using lowercase for variable names, unless you’re designing a library: ✓ let x = 1 ✓ let now = System.DateTime.Now We recommend using lowercase for all variable names bound in pattern matches, functions definitions, and anonymous inner functions. Functions may also use uppercase: ✗ let add I J = I+J ✓ let add i j = i + j Use uppercase when the natural convention is to do so, as in the case of matrices, proper nouns, and common abbreviations such as I for the “identity” function: ✓ let f (A:matrix) (B:matrix) = A+B ✓ let Monday = 1 ✓ let I x = x We recommend using camelCase for other values, including the following: � Ad hoc functions in scripts � Values making up the internal implementation of a module � Locally bound values in functions ✓ let emailMyBossTheLatestResults = ... ✓ let doSomething () = let firstResult = ... let secondResult = ... Syme_850-4C19.fm Page 557 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 557 Recommendation: Avoid using underscores in names. The F library uses underscore naming conventions to qualify some names. For example: � Suffixes such as _left and _right � Prefix verbs such as add_, remove_, try_, and is_, do_ � Prefix connectives such as to_, of_, from_, and for_ This is to ensure compatibility with OCaml. However, we recommend limiting the use of this style to the previous situations or avoiding it altogether, partly because it clashes with .NET naming conventions. Over time we expect the use of this style will be minimized in the F libraries. We recommend you avoid using two underscores in a value name and always avoid three or more underscores. ■Note No rules are “hard and fast,” and some F programmers ignore this advice and use underscores heavily, partly because functional programmers often dislike extensive capitalization. Furthermore, OCaml code uses underscores everywhere. However, beware that the style is often disliked by others who have a choice about whether to use it. It has the advantage that abbreviations can be used in identifiers without them being run together. Recommendation: Follow the .NET guidelines for exceptions. The .NET Framework Design Guidelines give good advice on the use of exceptions in the context of all .NET programming. Some of these guidelines are as follows: � Do not return error codes. Exceptions are the main way of reporting errors in frameworks. � 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 furthermore slow on .NET. Instead consider returning a None option value to indicate failure. � Do document all exceptions thrown by your code when a function is used incorrectly. � Where possible throw existing exceptions in the System namespaces. � Do not throw System.Exception or System.SystemException. �Use failwith, failwithf, raise System.ArgumentException, and raise System. InvalidOperationException as your main techniques to throw exceptions. ■Note Other exception-related topics covered by the .NET guidelines include advice on designing custom exceptions, wrapping exceptions, choosing exception messages, and special exceptions to avoid throwing (that is, OutOfMemoryException, ExecutionEngineException, COMException, SEHException, StackOverflowException, NullReferenceException, AccessViolationException, or InvalidCastException).Syme_850-4C19.fm Page 558 Tuesday, October 16, 2007 2:53 PM 558 CHAPTER 19 ■ DESIGNING F LIBRARIES Recommendation: Consider using option values for return types instead of raising exceptions. 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. Recommendation: Follow the .NET guidelines for value types. The .NET guidelines give good guidance about when to use .NET value types (that is, structs, which you saw introduced in Chapter 6). In particular, they recommend using a struct only when the following are all true: � A type logically represents a single value similar to a primitive type. � It has an instance size smaller than 16 bytes. � It is immutable. � It will not have to be boxed frequently (that is, converted to/from the type System.Object). Recommendation: Consider using explicit signature files for your framework. Explicit signature files were described in Chapter 7. Using explicit signatures files for frame- work code ensures that you know the full public surface of your API and can cleanly separate public documentation from internal implementation details. Recommendation: Consider avoiding the use of implementation inheritance for extensibility. Implementation inheritance is described in Chapter 6. In general, the .NET guidelines are quite agnostic with regard to the use of implementation inheritance. In F, implementation inheritance is used more rarely than in other .NET languages. The main rationale for this has been given in Chapter 6, where you also saw many alternative techniques for designing and implementing object-oriented types using F. However, implementation inheritance is used heavily in GUI frameworks. ■Note Other object-oriented extensibility topics discussed in the .NET guidelines include events and call- backs, virtual members, abstract types and inheritance, and limiting extensibility by sealing classes. Recommendation: Use properties and methods for attributes and operations essential to a type. For example: ✓ type HardwareDevice with ... member ID: string member SupportedProtocols: seqProtocol Consider supporting using methods for the intrinsic operations essential to a type: ✓ type HashTable'k,'v with ... member Add : 'k 'v - unit member ContainsKey : 'k - bool member ContainsValue : 'v - boolSyme_850-4C19.fm Page 559 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 559 Consider using static methods to hold a Create function instead of revealing object constructors: ✓ type HashTable'k,'v with static member Create : IHashProvider'k - HashTable'k,'v Recommendation: Avoid revealing concrete data representations such as records. Where possible, avoid revealing concrete representations such as records, fields, and implementation inheritance hierarchies in framework APIs. The rationale for this is that one of the overriding aims of library design is to avoid revealing concrete representations of objects. For example, the concrete representation of System. DateTime values is not revealed by the external, public API of the .NET library design. At runtime the Common Language Runtime knows the committed implementation that will be used throughout execution. However, compiled code does not itself pick up dependencies on the concrete representation. Recommendation: Use active patterns to hide the implementations of discriminated unions. Where possible, avoid using large discriminated unions in framework APIs, especially if you expect there is a chance that the representation of information in the discriminated union will undergo revision and change. For frameworks, you should typically hide the type altogether or use active patterns to reveal the ability to pattern match over language constructs. We described active patterns in Chapter 9. This does not apply to the use of discriminated unions internal to an assembly or to an application. Likewise, it doesn’t apply if the only likely future change is the addition of further cases and you are willing to require that client code be revised for these cases. Finally, active patterns can incur a performance overhead, and this should be measured and tested, though their benefits will frequently outweigh this cost. ■Note The rationale for this is that using large, volatile discriminated unions freely in APIs will encourage people to use pattern matching against these discriminated union values. This is appropriate for unions that do not change. However, if you reveal discriminated unions indiscriminately, you may find it very hard to version your library without breaking user code. Recommendation: Use object interface types instead of tuples or records of functions. In Chapter 5 you saw various ways to represent a dictionary of operations explicitly, such as using tuples of functions or records of functions. In general, we recommend you use object interface types for this purpose, because the syntax associated with implementing them is generally more convenient. Recommendation: Understand when currying is useful in functional programming APIs. Currying is the name used when functions take arguments in the “iterated” form, that is, when the functions can be partially applied. For example, the following function is curried: let f x y z = x + y + zSyme_850-4C19.fm Page 560 Tuesday, October 16, 2007 2:53 PM 560 CHAPTER 19 ■ DESIGNING F LIBRARIES This is not: let f (x,y,z) = x + y + z Here are some of our guidelines for when to use currying and when not to use it: � Use currying freely for rapid prototyping and scripting. Saving keystrokes can be very useful in these situations. � Use currying when partial application of the function is highly likely to give a useful residual function (see Chapter 3). � Use currying when partial application of the function is necessary to permit useful precomputation (see Chapter 8). � Avoid using currying in vanilla .NET APIs or APIs to be used from other .NET languages. When using currying, place arguments in order from the least varying to the most varying. This will make partial application of the function more useful and lead to more compact code. For example, List.map is curried with the function argument first because a typical program usually applies List.map to a handful of known function values but many different concrete list values. Likewise, you saw in Chapters 8 and 9 how recursive functions can be used to traverse tree structures. These traversals often carry an environment. The environment changes rela- tively rarely—only when you traverse the subtrees of structures that bind variables. For this reason, the environment is the first argument. When using currying, consider the importance of the pipelining operator; for example, place function arguments first and object arguments last. F also uses currying for let-bound binary operators and combinators: ✓ let divmod n m = ... ✓ let map f x = ... ✓ let fold f z x = ... However, see Chapters 6 and 8 for how to define operators as static members in types, which are not curried. Recommendation: Use tuples for return values, arguments, and intermediate values. Here is an example of using a tuple in a return type: ✓ val divmod : int - int - int int Some Recommended Coding Idioms In this section, we look at a small number of recommendations when writing implementation code, as opposed to library designs. We don’t give many recommendations on formatting, because formatting code is relatively simple for light indentation-aware code. We do give a couple of formatting recommendations that early readers of this book asked about.Syme_850-4C19.fm Page 561 Tuesday, October 16, 2007 2:53 PM CHAPTER 19 ■ DESIGNING F LIBRARIES 561 Recommendation: Use the standard operators. The following operators are defined in the F standard library and should be used wher- ever possible instead of defining equivalents. Using these operators tends to make code much easier to read, so we strongly recommend it. This is spelled out explicitly because OCaml doesn’t support all of these operators, and thus F users who have first learned OCaml are often not aware of this. f g forward composition g f reverse composition x f forward pipeline f x reverse pipeline 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 bitwise left shift x y bitwise right shift x y bitwise left shift, also for working with enumeration flags x &&& y bitwise right shift, also for working with enumeration flags x y bitwise left shift, also for working with enumeration flags x && y lazy/short-cut "and" x y lazy/short-cut "or" Recommendation: Place pipeline operator at the start of a line. People often ask how to format pipelines. We recommend this style: let methods = System.AppDomain.CurrentDomain.GetAssemblies List.of_array List.map (fun assem - assem.GetTypes()) Array.concat Recommendation: Format object expressions using the member syntax. People often ask how to format object expressions. We recommend this style: let thePlayers = new Organization() with member x.Chief = "Peter Quince" member x.Underlings = "Francis Flute"; "Robin Starveling"; "Tom Snout"; "Snug"; "Nick Bottom" interface IDisposable with member x.Dispose() = () Syme_850-4C19.fm Page 562 Tuesday, October 16, 2007 2:53 PM 562 CHAPTER 19 ■ DESIGNING F LIBRARIES ■Note The discussion of F design and engineering issues in Chapters 18 and 19 has necessarily been limited. In particular, we haven’t covered topics such as aspect-oriented programming, design and modeling methodologies, software quality assurance, or software metrics, all of which are outside the scope of this book. Summary In this chapter, we covered some of the rules you might apply to library design in F, particularly taking into account the idioms and traditions of .NET. We also considered some of the elements of “functional programming” design methodology, which offers many important and deep insights. Finally, we gave some specific suggestions on designing .NET and F libraries. That concludes our tour of F, and we hope you enjoy a long and productive career coding in the language.