Login | Register   
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

An Introduction to F# for Functional Programming : Page 2

Functional programming with F# is much more than writing good code. It is about enjoying writing code quickly and effectively.


advertisement
Understanding Immutability, Closures, and Type Inference
A fundamental concept in functional programming is immutability: The values you create do not change. This is in sharp contrast to object-oriented or imperative languages where mutation via variables is omnipresent. Instead of mutating values, you create new mutated copies by replacing your ordinary imperative-style void functions that mutate their parameters in-situ with functions that return a mutated copy of their parameters. You may wonder: Isn’t creating new copies wasteful? Not quite, as it turns out the compiler is able to optimize much of the copy-then-mutate operations away by applying some clever tactics, which allows it to perform better in certain situations than imperative compilers.

Why do you need variables then? Strictly speaking, you don’t (unless you want to write imperative code). Consider the code segment below:

let printSumDoubled x y = printf “The result is = %d\n” ((x+y)*2) let _ = let a, b = 172, 201 printSumDoubled a b let a, b = 215, 347 printSumDoubled a b

The first line defines a function that doubles the sum of its two arguments and prints the result. Note how no type annotations were needed, the compiler figures out what parameters have what types by looking at how they are used. Here, adding x to y gives it away that both x and y must be integers (by default for arithmetic operators, unless you annotate the parameters otherwise). Another noteworthy piece is that you don’t have to parenthesize the formal parameters nor separate them by commas. You can do that, but in that case your function no longer takes two arguments but instead one tuple argument, and you need to change each invocation similarly to printSumDoubled(a, b). Don’t worry, the compiler tells you if you are passing too many arguments when calling functions, however, passing fewer gives you a nifty way to create closures—functions that carry their own state bag:


let printSumWithFiveDoubled = printSumDoubled 5

Here printSumWithFiveDoubled is a function that expects a single integer argument, adds five to it, then doubles and prints the result. It is basically an instantiation of the printSumDoubled function, and this becomes more apparent in the following equivalent definition:

let printSumWithFiveDoubled y = printSumDoubled 5 y

The let bindings in the above snippet simply combine individual assignments on a single line. However, under the hood there is a lot more going on. In the first line, the snippet matches the value (172, 201) is matched against the pattern on the left (a, b). Because that's a straightforward match, it results in binding those numbers to a and b, respectively. Naturally, you could pattern match against arbitrary complex expressions and create numerous bindings at once (or none at all—as in the top-level binding to _ which simply means throwing away the value of the computed expression). Functions that seemingly return no value (those that are void in C#, for instance) have the unit return type, which actually has a single "no-value" represented as (). The preceding example ignores this empty value.

The key piece is understanding that bindings are different from variables in the ordinary sense: they are nothing more than names for certain values, and reassigning them to new values simply shadows the previous ones. In effect, the above code is equivalent to:

let printSumDoubled x y = printf “The result is = %d\n” ((x+y)*2) let _ = let a, b = 172, 201 printSumDoubled a b let newA, newB = 215, 347 printSumDoubled newA newB

Enhancing Your Types and Pattern Matching
Using type augmentations is an effective way to manage the functionality provided around your types (see Listing 1).

Type definitions usually go hand-in-hand with some fundamental control abstraction mechanisms that operate on the values that belong to those types: pattern matching (against the structure of a value—such as matching a tree with a certain shape, or a list of a certain size and content), anonymous functions (or so-called lambda expressions: functions created on the fly and without a name), and aggregate higher-order functions (that apply functions on enumerable data). For instance, in Listing 1, Seq.sumByFloat is a static aggregate function on the Seq type (the type representing all enumerable types, and for which seq is a standard type abbreviation, as used in the type definition for Drawing) that takes an anonymous function (that extracts the perimeter of a shape), applies it on all shapes in the drawing, and sums up the results to get an indication of the amount of ink needed for the drawing.

Piping and Understanding Common Aggregate Functions
You may wonder about the definition of TotalInkNeeded using the pipe ( |>) operator. This operator is defined in the F# standard library as:

let (|>) x f = f x

Being an infix operator, the pipe sends (pipes) its left operand to the right one, giving an elegant way to express a function call. This has at least two nice consequences: first, you can typically get by without having to parenthesize your arguments, second, you actually aid the compiler in type inference (as you are pushing type information from the left by supplying values of known types).

The sumByFloat method is a special case of folding, which is a more general functional pattern that takes a function and an initial accumulator and weaves them through a collection by applying the function with the initial accumulator to the first element, then with the result to the second, and so on, resulting in the final value of the accumulator. The F# standard library supports folding, mapping (applying a function to every element in a collection to construct a new collection with the resulting values), and iterating (visiting every element) on all generic enumerable types. These aggregate operations and many of the more special operations derived from them are the most heavily used tools in any functional programmer’s toolset.

And finally, test out your new types in an interactive session easily, using the code from Listing 1. For example, the following code creates 20 new shapes (using a sequence comprehension and piping the result to the Drawing constructor) then calculates the total ink needed to draw them:

> let a = seq { for i in 1 .. 20 -> if i % 3 = 0 then Circle (20, 20, i) elif i % 3 = 1 then Rectangle (20, 20, 20+i*2, 20+i) else Shape.Square (20, 20, i) } |> Drawing;; val a : Drawing > a.TotalInkNeeded;; val it : float = 1123.840674

Conclusion
In this article, you saw some of the key functional programming concepts and how you can quickly get started using them in F#. You looked at some basic type definitions, simple, function, and structured values, infinite lazy sequences, and types with augmented members that used aggregate higher-order functions, lambda expressions, and the piping operator. You also created an enumeration of values representing graphical shapes using a sequence comprehension. In the next article of this series, you will learn about some of these features in detail and also about the more advanced functional constructs that can really drive your F# productivity curve.


Adam Granicz is the CEO of IntelliFactory, the primary place for F# development, training and consulting services, and the coauthor of Expert F#, the most comprehensive guide on F# coauthored with Don Syme, the designer of the language. He has done research in functional programming languages and holds a Masters degree from the California Institute of Technology.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap