As
promised, today I'm demonstrating a compelling way in which
F# uses
tuples to make .NET programming more elegant.
A question that comes up early in F# demonstrations is, "Can I use F# to
access code written in my favorite .NET language, <BLANK>?" The answer is an
emphatic yes. F# is a first-class .NET citizen that compiles to
the same IL as
any other .NET language. Consider the following code:
> #light
- open System.Collections.Generic
-
- let d = new Dictionary<int, string>()
- d.Add(1, "My")
- d.Add(2, "F#")
- d.Add(3, "Dictionary");;
val d : Dictionary<int,string>
> d;;
val it : Dictionary<int,string> = dict [(1, "My"); (2, "F#"); (3, "Dictionary")]
The above code1 instantiates a new
System.Collections.Generic.Dictionary<TKey,
TValue> for int and string, and adds three key/value pairs to it.
Note that Dictionary is not written in F#. It is part of the .NET base class
library, written in C#.
Retrieving values from d is easy.
We simply pass the value's key to the dictionary's indexer like so:
> d.[1];;
val it : string = "My"
> d.[3];;
val it : string = "Dictionary"
However, if we pass a key that isn't found in the
dictionary, an exception is thrown.2
> d.[4];;
System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
at System.ThrowHelper.ThrowKeyNotFoundException()
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at <StartupCode$FSI_0013>.FSI_0013._main()
stopped due to error
Fortunately, Dictionary provides a
function that allows us to query using an invalid key without throwing an exception. This
function,
TryGetValue,
has the following signature (shown in C#):
bool TryGetValue(TKey key, out TValue value)
The purpose of TryGetValue is obvious. If key is found, the
function returns true
and the value is returned in the output parameter3.
If key is not found, the
function returns false and value contains some
throwaway data.
The C# code below demonstrates how this function might be used.
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var d = new Dictionary<int, string>();
d.Add(1, "My");
d.Add(2, "C#");
d.Add(3, "Dictionary");
string v;
if (d.TryGetValue(4, out v))
Console.WriteLine(v);
}
}
So, how can we use this function in F#? Well, there're a few ways.
The first approach is almost exactly the same as the C# version above. First,
we declare a variable to pass as the output parameter. Note that
this variable must be declared as mutable so TryGetValue can modify it.
> let mutable v = "";;
val mutable v : string
Now, we can call TryGetValue, passing v by reference.
> d.TryGetValue(1, &v);;
d.TryGetValue(1, &v);;
-----------------^^^
stdin(19,17): warning: FS0051: The address-of operator may result in non-verifiable code.
Use only when passing byrefs to functions that require them.
val it : bool = true
> v;;
val it : string = "My"
OK. That worked but displayed an ugly warning about non-verifiable code.
Yikes!
Fortunately, F# provides another way to declare variables which support
mutation:
reference cells.4
Declaring a variable as a reference cell is trivial:
> let v = ref "";;
val v : string ref
We can pass the reference cell into TryGetValue without receiving
that nasty warning.
> d.TryGetValue(2, v);;
val it : bool = true
> !v;;
val it : string = "F#"
That's much better.
At this point, many of you are probably thinking, "Wait a minute! Wasn't this
article supposed to be about tuples? What's all this
mutable-variable-output-parameter stuff?" Don't worry. There's a method to my
madness. Are you ready?
Consider what happens if we call TryGetValue without
specifying a variable for the output parameter:
> let res = d.TryGetValue(3);;
val res : bool * string
> res;;
val it : bool * string = (true, "Dictionary")
Did you catch that? When calling a function containing output parameters in F#, you
don't have to specify variables for them. The F# compiler will automatically
consolidate the function's result and output parameters into a tuple (in
this case, a pair).
Awesome! If you were paying attention
last
time, you've probably already realized that we can bind the
TryGetValue
call
to a pattern that extracts the values from the resulting pair.
> let res, v = d.TryGetValue(2);;
val res : bool
val v : string
> res;;
val it : bool = true
> v;;
val it : string = "F#"
Now, we can easily query our dictionary using an invalid key without an
exception being thrown. Best of all, we don't have to declare an awkward mutable variable to
store the value. What takes two lines of code in C# consumes just one in F#.
> let res, v = d.TryGetValue(4);;
val res : bool
val v : string
> res;;
val it : bool = false
It is the attention to detail that makes it a joy to code with F#. This is just one example of how F# can
consume .NET framework classes in ways more
elegant than even C#, the
lingua franca of the .NET universe!
I haven't decided what the next article will cover yet. Are there any
requests? Feel free to email them to dustin AT diditwith.net.
1The #light directive in the
first line of the code sample
enables the
F# lightweight syntax. We'll look closer at this in a future article.
2This might be frustrating to users of the
System.Collections.Hashtable class from .NET Framework 1.0. Unlike
Dictionary, Hashtable returns null when a key isn't found
rather than throwing an exception. The reason for this behavior difference is
detailed
here.
3Normally, I would consider the use of output parameters to be a
code smell. However, TryGetValue is an example
of a scenario where an output parameter is justified.
4We'll be looking more deeply into reference cells in a future article.