Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


F# 101 : Page 2

F#, the latest member of the Visual Studio family of languages, offers some enticing advantages over C# and Visual Basic, stemming from its functional-object fusion nature.

"Let" There Be F#
The core of the F# language is, in many ways, the let expression. In the grand tradition of mathematics, where let was always used to establish the preconditions of a mathematical proof, F#'s let binds a value to an identifier, where the value can be either a traditional value, such as "12" or "Amanda" or some other constant, or a function taking zero or more inputs into the function for processing. Thus, in the following:

let x = 5 let y = 12 let xtimesy x y = x * y

F# recognizes that x is bound to the value 5, y is bound to the value 12, and that xtimesy is bound to the function value x * y. Already, even in this simple code snippet, one of F#'s core concepts comes to the fore: Not only does F# realize that the values of "x" and "y" have to be integers (System.Int32, in fact) because of the fact that they are each bound to an integer constant, but also that the inputs to xtimesy must be integers, based on the fact that the definition is doing multiplication.

In the grand tradition of mathematics, where "let" was always used to establish the preconditions of a mathematical proof, F#'s "let" binds a value to an identifier, where the value can be either a traditional value, such as "12" or "Amanda" or some other constant, or a function taking zero or more inputs into the function for processing.
This type inference behavior isn't perfect, by the way, because F# has inferred that the inputs should be integers, passing floating-point values in will cause F# to register it as an error:

printfn "xtimesy 5 12 = %d" (xtimesy 5 12) printfn "xtimesy 5.0 12.0 = %d" (xtimesy 5.0 12.0)

The first line will be OK, but the second will be flagged as an error by the Visual Studio environment, since the only definition of xtimesy currently requires integers. (Remember, F# is a statically typed language, despite what the terseness and lack of type information in the code may imply.) To fix this, you need a new definition of xtimesy that accepts floats, as well.

Actually, as it turns out, F# doesn't permit overloading of functions at this level, though it is OK with doing it inside of types; to make the above code work, you have to write it as a function with a different name, such as xytimesyf:

let xtimesy x y = x * y let xtimesyf (x:double) (y:double) = x * y printfn "xtimesy 5 12 = %d" (xtimesy 5 12) printfn "xtimesyf 5.0 12.0 = %f" (xtimesyf 5.0 12.0)

Although the preceding code is perfectly valid, the functional community rarely performs this kind of overloading in F#, thanks to other features of the language, such as automatic inference of generic arguments.

Note that the use of x and y in the function xtimesy is not bound against the x and y previously defined—like C#, F# has scope rules that say that names "inside" a new scope trump names from an outer scope. This applies, by the way, to nested functions just as it does to nested classes in C#.

In the second definition of xtimesy, the function definition chooses not to allow F# to infer the arguments (which it could do, since later in the program xtimesyf is used with floating-point values), but to explicitly declare the inputs to the function, each as a double. The parentheses are necessary here to help separate the first argument from the second; without them, an error occurs.

Normally, in F#, you can leave off the type designators, leaving F# to, in many cases, infer even more generic arguments about code than the original developer intended. For example, consider this function, which takes a tuple (pair) of arguments and reverses them:

let swap (a, b) = (b, a)

F# interprets this function to take two arguments, of any particular type (they don't even have to be the same type), essentially turning this into a generic function along the lines of this equivalent C# function:

public static Tuple<U, T> swap<T, U>(T a, U b) { return new Tuple<U, T>(b, a); }

In fact, glancing at the implementation of the F# code in Reflector, this is precisely what's displayed.

If this were the sum total of F#'s programming capabilities, the language wouldn't merit much in the way of interest—it would still merit some, mind you, because these functions are exposed as static methods on a namespace defined to be the same as the file name (so, for example, the file1.exe assembly exports methods such as File1::xtimesy in the above example), and are thus usable from C#. But F# can also "do objects" as it turns out.

Classes in F#
Building a class in F# is actually fairly straightforward, with some interesting caveats and differences from traditional C# classes:

type Person(firstName: string, lastName: string, age: int) = static member Nobody = Person("", "", 0) member this.FirstName = firstName member this.LastName = lastName member this.Age = age override this.ToString() : string = System.String.Format("[Person {0} {1} ({2} years old)]", firstName, lastName, age) let p = Person("Ted", "Neward", 37) printfn "Creating new Person %A" p

The Person type here is certainly not complicated by any means, but several F# syntactic constructs make the class shorter and more succinct.

For example, consider the Person constructor. In C#, this would be a separate method whose name must match the type and have no return type, and the body of which is often taking parameters and storing them directly into fields of the same or similar name. This pattern is so common that in F# you define a primary constructor on the type definition line itself (the line type Person, above) and the implementation naturally defines fields of those names and types and generates the logic to store the constructor parameters into those fields.

Pattern matching provides a powerful linguistic construct only found in functional languages.
Or, as another case of F#'s succinctness, consider how type inferencing affects the definition of the FirstName, LastName, and Age properties: You don't need to write type information there, because F# can infer it all from the definition around the fields (firstName, lastName, and age, respectively) used in the property definition.

Similarly, to create a new instance of the Person type, F# client code simply calls the Person constructor as a direct method call, rather than keying off of the new keyword as in C#. Again, this simple syntactic change reduces code verbosity—albeit only a single keyword—and helps make the language more readable.

Also, fairly obviously, F# uses no Begin/End or curly brace characters to delimit the start and end of the type declaration, nor does it use them in looping and other nesting constructs, such as the if expression discussed later. To avoid any such obvious delimiter, F# borrows a trick from Python, that of significant whitespace: in an F# program, the language determines whether a block of code is part of the nested block or the outer block based on its indentation level. Thus, in the definition of ToString above, the implementation of the method must be one indentation level "deeper" than the indentation level at which the other member definitions sit.

This aspect of F# is one of its more controversial features (just as it is with Python), but for the most part, it fades into the background during coding. However, one thing that may bite the budding F# programmer is that Visual Studio will complain if the editor is set to use tabs instead of spaces for indentation, so make sure to clear that option in the Tools → Options dialog under the TextEditor → F# node.

Comment and Contribute






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