F# is a functional language being developed by Microsoft Research.
Summary
In this article you will learn how to pass parameters and retrieve results from a F# library. Although F# can be used to program everything in my humble opinion it should be used in areas where it will make the code more robust, more easier to understand or support, but it should not be used for ordinary tasks where today imperative languages do their job just fine. In other words we should use both of them. F# and C# may both be .NET using the Common Type System, but communication between them might not be that straight forward, at least not in the beginning. In this demos we will be using the Task Parallel Library which is another cool library that optimizes you code running on multi-core machines. TPL is available for all .NET languages and in this demo we using the TPL CTP June 2008. The three demos do the same thing, they only differ in input and output parameters usage. The F# code resides in a separate project as a F# library. The purpose of the F# code is to download web pages.
Credits:
Credit goes to this
polyglot article, the website
stackoverflow.com and what I've learned thanks to the
F# community.
You can browse/download the code from
http://code.google.com/p/fsharpdemos/source/browse/#svn/trunk. The version of F# used is 1.9.6.2.
The three F# demos start with:
#light
open System.IO
open System.Net
open Microsoft.FSharp.Control.CommonExtensions
open System.Threading
open System.Threading.Tasks;
open System.Threading.Collections
open System
Sample 1let download
(urls:
string list
) =
seq
{ use results =
new BlockingCollection<
(string <strong>
string)>
() use pagesRemain =
new CountdownEvent
(1) let <u> = Task.
Create(fun </u> ->
urls |> List.
iter(fun url ->
let wc =
new WebClient
() wc.
DownloadStringCompleted.
Add(fun args ->
if args.
Error =
null then
results.
Add(((args.
UserState :?>
string), args.
Result)) if pagesRemain.
Decrement() then
results.
CompleteAdding() ) pagesRemain.
Increment() wc.
DownloadStringAsync(new Uri
(url
), url
) ) if pagesRemain.
Decrement() then results.
CompleteAdding() ) for result
in results.
GetConsumingEnumerable() do yield result
}
The problem here is that the input parameter is a F# list which quite different from the C# List. This means that:
- you need to include the FSharp.Core assembly in your C# application
- build that F# list and pass it
string[] scores = { "http://osnews.com", "http://cnn.com"};
//We create an empty list and move backwards because we are adding values before what we've just added and this way we keep the C# value order.
var fs_scores = Microsoft.FSharp.Collections.List<string>.get_uniq_Empty();
for (int i = scores.Length - 1; i >= 0; i--)
{
// In functional programming lists are build from tuples as each tuple links to the next.
// Cons(scores[i], fs_scores) means that we attach the next C# element to the previously created list
// The first time the list had one tuple that has the last value of the C# array and links to empty list
// which acts as a sentinel.
fs_scores = Microsoft.FSharp.Collections.List<string>.Cons(scores[i], fs_scores);
}
//The result also comes in the form of tuples:
foreach (Microsoft.FSharp.Core.Tuple<string, string> pair in InteroperabilityDemo1.download(fs_scores))
{
Console.WriteLine(pair.Item2);
}
Sample 2
Here we replace the input F# list with type easily recognized by C#
let download
(array : ResizeArray<string>
) =
seq
{ use results =
new BlockingCollection<
(string </strong>
string)>
() use pagesRemain =
new CountdownEvent
(1) let urls = List.
of_seq(array
) let <u> = Task.
Create(fun </u> ->
urls |> List.
iter(fun url ->
let wc =
new WebClient
() wc.
DownloadStringCompleted.
Add(fun args ->
if args.
Error =
null then
results.
Add(((args.
UserState :?>
string), args.
Result)) if pagesRemain.
Decrement() then
results.
CompleteAdding() ) pagesRemain.
Increment() wc.
DownloadStringAsync(new Uri
(url
), url
) ) if pagesRemain.
Decrement() then results.
CompleteAdding() ) for result
in results.
GetConsumingEnumerable() do yield result
}
The calling code from C# is:
string[] scores = { "http://osnews.com", "http://cnn.com"};
foreach (Microsoft.FSharp.Core.Tuple<string, string> pair in InteroperabilityDemo2.download(scores.ToList<string>()))
{
Console.WriteLine(pair.Item2);
}
The code here definitely shorter, but unfortunately still use the FSharp.Core for the tuples in the output.
Sample 3
Here we add an additional structure "dotnetresult" to make sure the result is recognized with no problems in C#.
let download
(arr : ResizeArray<string>
) =
use results =
new BlockingCollection<
(string *
string)>
() use pagesRemain =
new CountdownEvent
(1) let dotnetresult =
new System.
Collections.
Generic.
Dictionary<string,string>
() let urls = List.
of_seq(arr
) let <u> = Task.
Create(fun </u> ->
urls |> List.
iter(fun url ->
let wc =
new WebClient
() wc.
DownloadStringCompleted.
Add(fun args ->
if args.
Error =
null then
results.
Add(((args.
UserState :?>
string), args.
Result)) if pagesRemain.
Decrement() then
results.
CompleteAdding() ) pagesRemain.
Increment() wc.
DownloadStringAsync(new Uri
(url
), url
) ) if pagesRemain.
Decrement() then results.
CompleteAdding() ) //(fst result) returns the url and (snd result) returns the html for result
in results.
GetConsumingEnumerable() do dotnetresult.
Add((fst result
),
(snd result
)) dotnetresult
An the calling C# code:
string[] scores = { "http://osnews.com", "http://cnn.com"};
Dictionary<string, string> bc = InteroperabilityDemo3.download(scores.ToList<string>());
foreach (string key in bc.Keys)
{
Console.WriteLine(bc[key]);
}
Now we do not need FSharp.Core and no one should guess in what language our F# library has been written.
Update:
Developing standard .NET libraries in F# webcast by Tomas Petricek