Rash thoughts about .NET, C#, F# and Dynamics NAV.


"Every solution will only lead to new problems."

Wednesday, 12. May 2010


“FAKE – F# Make” 1.20.0 released

Filed under: F#,FAKE - F# Make — Steffen Forkmann at 12:26 Uhr

Today I released a new bugfix release for “FAKE – F# Make”. We fixed some path and logging issues and as a new feature we introduced the @@ operator which allows to combine paths.

Tags: , ,

Tuesday, 24. November 2009


Generating an IObservable<T> from an IEvent in F#

Filed under: F# — Steffen Forkmann at 12:39 Uhr

Yesterday I showed how we can map some of the Rx operators to an API which looks more like the F# base classes. Today I wanted to use these mapped operators in a WPF-application written in F#.

F# gives us a nice way to use events as first class citizen (via IEvent) but these events implement their own version of IObservable<T> (in FSharp.Core.dll), which is unfortunately incompatible with the Rx version and therefore with the mapped API.

The solution I found is to wrap the F# IEvent with a Rx IObservable<T>:

/// Generates an observable from an IEvent

let fromEvent (event:IEvent<_,_>) =      

  Observable.Create<_>(fun x ->

    event.Subscribe x.OnNext |> ignore

    new System.Action(fun () -> ()))     

Now we are able to use the WPF events as observables:

// Register ListBox Commands

listBox1.KeyDown

  |> Observable.fromEvent

  |> Observable.filter (fun args -> args.Key = Key.Delete)

  |> Observable.subscribe deleteElement

I am interested if someone has a different and maybe better solution to this problem.

Updated: 21.12.2009 – Observable.Context is no longer supported by Rx ==> Removed

Tags: ,

Monday, 23. November 2009


Mapping the Reactive Framework (Rx) operators for F#

Filed under: F# — Steffen Forkmann at 13:35 Uhr

The “Reactive Extensions for .NET (Rx)” comes with lot’s of operators for using IObservable<T>. This code mimics the signature of the default F# sequence combinators and allows to use observables like sequences. It is a similar approach like Matthews Podwysocki’s blog post about mapping the IParallelEnumerable.

I will update this post from time to time to include more of the operators.

  • Update: 25.11.2009 – new operators mapped
  • Update: 21.11.2009 – Updated to new Rx release

module RxExtensions.Observable

 

open System.Linq

open System

open System.Threading

open System.Windows.Threading

 

type ‘a observable = IObservable<’a>

type ‘a observer = IObserver<’a>

 

/// converts a lambda in a System.Action

let asAction f = new System.Action(f)

 

/// System.Action whichs does nothing

let doNothing = asAction (fun () -> ())

 

/// Creates an observer

let createObserver next error completed =

  {new System.IObserver<_> with

      member this.OnCompleted() = completed()

      member this.OnError(e) = error e

      member this.OnNext(args) = next args}

 

/// Creates a new observable

let create f =

  Observable.Create<_>(fun x ->

    f x

    doNothing)  

 

/// Creates a observable from a async

let ofAsync async =

  create

    (fun obs ->

       Async.StartWithContinuations

         (async,obs.OnNext,obs.OnError,obs.OnError))

 

/// Gets a dispatcher Schdeuler for the current dispatcher

let getDispatcherScheduler _ =

  new DispatcherScheduler(Dispatcher.CurrentDispatcher)

 

/// Generates an observable from an IEvent

let fromEvent (event:IEvent<_,_>) = create (fun x -> event.Add x.OnNext)

 

/// Generates an empty observable

let empty<’a> = Observable.Empty<’a>()

 

/// Takes the head of the elements

let head = Observable.First

 

/// Merges the two observables

let mergeWith obs1 obs2 = Observable.Merge(obs2, obs1)

 

/// Merges all observables

let mergeAll (observables:IObservable<IObservable<’a>>) =

  Observable.Merge observables

 

/// Merges all observables

let merge (observables:(IObservable<’a>) seq) =

  Observable.Merge observables

 

/// Creates a range as an observable

let range start count = Observable.Range(start, count)

 

/// Converts a seq in an observable

let toObservable (seq: ‘a seq) = Observable.ToObservable seq

 

/// Converts a observable in a seq

let toEnumerable = Observable.ToEnumerable

 

/// Subscribes to the Observable with all 3 callbacks

let subscribeComplete next error completed (observable: ‘a observable) =

   observable.Subscribe(

     (fun x -> next x),

     (fun e -> error e),

     (fun () -> completed()))

 

/// Subscribes to the Observable with a

/// next and an error-function

let subscribeWithError next error observable =

  subscribeComplete next error (fun () -> ()) observable

 

/// Subscribes to the Observable with just a next-function

let subscribe next observable =

  subscribeWithError next ignore observable

 

/// throttles the observable for the given interval

let throttle interval observable =

  Observable.Throttle(observable,interval)   

 

/// throttles the observable scheduled on the current dispatcher

let throttleOnCurrentDispatcher interval observable =

  Observable.Throttle(

     observable,getDispatcherScheduler(),interval)

 

/// samples the observable at the given interval

let sample interval observable =

  Observable.Sample(observable,interval)   

 

/// samples the observable at the given interval

/// scheduled on the current dispatcher

let sampleOnCurrentDispatcher interval observable =

  Observable.Sample(

    observable,getDispatcherScheduler(),interval)

 

/// returns the observable sequence that reacts first.

let takeFirstOf2Reactions obs1 obs2 =

  Observable.Amb(obs1,obs2)

 

/// returns the observable sequence that reacts first.

let amb (obs: IObservable<’a> seq) =

  Observable.Amb obs  

 

/// returns the observable sequence that reacts first.

let takeFirstReaction (obs: IObservable<’a> seq) =

  Observable.Amb obs  

 

/// Matches when both observable sequences

/// have an available value.

let both obs1 obs2 = Observable.And(obs1,obs2)

 

/// Merges two observable sequences

/// into one observable sequence.

let zip obs1 obs2 =   

   Observable.Zip(obs1, obs2, Func<_,_,_>(fun a b -> a, b))

 

/// Merges two observable sequences into one observable sequence

/// whenever one of the observable sequences has a new value.

///    ==> More results than zip

let combineLatest obs1 obs2 =   

   Observable.CombineLatest(

     obs1, obs2, Func<_,_,_>(fun a b -> a, b))    

 

/// Concats the two observables to one observable

let concat observable =

  Observable.SelectMany(

    observable,

    Func<_,_>(fun (x:IObservable<’a>) -> x))

 

/// maps the given observable with the given function

let map f observable =

  Observable.Select(observable,Func<_,_>(f))  

 

/// maps the given observable with the given function

let mapi f observable =

  Observable.Select(observable,Func<_,_,_>(fun x i ->f i x))  

 

/// Filters all elements where the given predicate is satified

let filter f observable =

  Observable.Where(observable, Func<_,_>(f))

 

/// Splits the observable into two observables

/// Containing the elements for which the predicate returns

/// true and false respectively

let partition predicate observable =

  filter predicate observable,

  filter (predicate >> not) observable

 

/// Skips n elements

let skip n observable = Observable.Skip(observable, n)

 

/// Skips elements while the predicate is satisfied

let skipWhile f observable =

  Observable.SkipWhile(observable, Func<_,_>(f))

 

/// Runs all observable sequences in parallel

/// and combines their first values.

let forkJoin (observables: (‘a observable) seq) =

  Observable.ForkJoin observables

 

/// Counts the elements

let length = Observable.Count

 

/// Takes n elements

let take n observable =

  Observable.Take(observable, n)  

 

/// Determines whether the given observable is empty 

let isEmpty observable = Observable.IsEmpty observable

 

/// Determines whether the given observable is not empty 

let isNotEmpty observable = not (Observable.IsEmpty observable)

 

/// Determines whether an observable sequence

/// contains a specified value

/// which satisfies the given predicate

let exists predicate observable =

  observable

    |> skipWhile (predicate >> not)

    |> isNotEmpty

 

/// Continues an observable sequence that is terminated

/// by an exception with the next observable sequence.

let catch (newObservable:IObservable<’a>) failingObservable =

  Observable.Catch(failingObservable,newObservable) 

 

/// Takes elements while the predicate is satisfied

let takeWhile f observable =

  Observable.TakeWhile(observable, Func<_,_>(f))

 

/// Iterates through the observable

/// and performs the given side-effect

let perform f observable =

  Observable.Do(observable,fun x -> f x)

 

/// Invokes finallyAction after source observable

/// sequence terminates normally or by an exception.

let performFinally f observable =

  Observable.Finally(observable,fun _ -> f())

 

/// Folds the observable

let fold f seed observable =

  Observable.Aggregate(observable, seed, Func<_,_,_>(f))  

 

/// Retruns an observable from a async pattern 

let fromAsync beginF endF =

   Observable.FromAsyncPattern<_>(

     Func<_,_,_>(fun x y -> beginF(x,y)),

       (fun x -> endF x)).Invoke()

 

/// Runs all observable sequences in parallel

/// and combines their first values.

let subscribeAll next observables =

  observables |> Seq.map (subscribe next) |> Seq.toList     

 

type IObservable<’a> with

  /// Subscribes to the Observable with just a next-function

  member this.Subscribe(next) =

    subscribe next this

 

  /// Subscribes to the Observable with a next

  /// and an error-function

  member this.Subscribe(next,error) =

    subscribeWithError next error this

 

  /// Subscribes to the Observable with all 3 callbacks

  member this.Subscribe(next,error,completed) =

    subscribeComplete next error completed this

 

open System.Net

 

type WebRequest with

  member this.GetRequestStreamAsync() =

    fromAsync

     this.BeginGetRequestStream

     this.EndGetRequestStream

 

  member this.GetResponseAsync() =

    fromAsync

      this.BeginGetResponse

      this.EndGetResponse

 

  member this.GetResponseStreamAsync() =

    fromAsync

      this.BeginGetRequestStream

      this.EndGetRequestStream

 

type Async<’a> with

  member this.ToObservable() = ofAsync this

Tags: , , ,

Tuesday, 6. October 2009


“FAKE – F# Make” Version 0.10 released

Filed under: F#,FAKE - F# Make — Steffen Forkmann at 11:59 Uhr

I just released a new version of my Open Source Build Automation Framework “FAKE – F# Make”. You can read more about FAKE on the project website or in the Getting started with "FAKE – F# Make"-article.

Although the new release contains many bugfixes, I only want to show the two major improvements here.

1. FAKE 0.10 uses FSI instead of FSC

From now on FAKE uses the “F# Interactive” (fsi.exe) instead of the F# Compiler (fsc.exe) to run the build scripts, which brings two major improvements.

No TempPath for compiled binaries needed

Due to the fact that FAKE scripts are no longer compiled at the beginning of the build process, we don’t need a temporary folder for the created binaries.

Loading modules at runtime

The #load command in F# scripts allows us to load modules at runtime. Now we are able to put reusable Targets or TargetTemplates (see below) into external build script files.

2. TargetTemplates

TargetTemplates provide an easy way to reuse common Targets. Let’s consider a (very) small sample:

Target "TraceHello" (fun () ->

  trace "Hello World from FAKE"

)

This Target “TraceHello” traces a “Hello World” string into our build log. Now we want it to be slightly more generic and to trace a custom string. We can do this by using a TargetTemplate:

/// createTraceTarget: string -> string -> Target

let createTraceTarget = TargetTemplate (fun s ->

  trace s

)

Now we have a template (or a function which generates targets) that gets a string for the target name and a string for the trace text and generates  a usable target:

createTraceTarget "TraceHello" "Hello World from FAKE"

createTraceTarget "Trace2" "Trace another text"

Of course the TargetTemplate function is generic and can be used with any tuple as parameter:

/// createTraceTarget: string -> string*int -> Target

let createTraceTarget = TargetTemplate (fun (s,d) ->

  trace s

  trace <| sprintf "my int: %d" d

)

 

createTraceTarget "TraceHello" ("Hello World from FAKE",2)

createTraceTarget "Trace2" ("Trace another text",42)

Tags: , , ,

Wednesday, 1. April 2009


Getting started with “FAKE – F# Make” – Get rid of the noise in your build scripts.

Filed under: C#,English posts,F#,FAKE - F# Make,Informatik,NaturalSpec,Tools — Steffen Forkmann at 21:02 Uhr

In this tutorial I will describe how you can set up a complete build infrastructure with “FAKE – F# Make”. You will learn:

  • How to automatically compile your C# or F# projects
  • How to automatically run NUnit UnitTests on your projects
  • How to zip the output to a deployment folder
Install F#

“FAKE – F# Make” is completely written in F# and all build scripts will also be written in F#, but this doesn’t imply you have to learn programming in F#. In fact the “FAKE – F# Make” syntax is very easy to learn. But if you need to you can use the complete power of F# and the .NET Framework.

But in order to get things working we need to install the F# environment. You can download the F# April 2010 CTP from the Microsoft F# Developer Center or install Visual Studio 2010.

Download Calculator Sample

Now download the latest CalculatorSample-*.zip from the FAKE Download site. This sample includes 3 tiny projects and basically the following structure:

  • src\app
    • Calculator (Command line)
    • CalculatorLib (Class library)
  • src\test
    • Test.CalculatorLib (NUnit test library)
  • tools
    • FAKE Assemblies
    • NUnit
  • build.bat
  • build.fsx
  • completeBuild.bat
  • completeBuild.fsx
  • Calculator.sln
Getting “FAKE – F# Make” started

Open the file tools\FAKE\FakeLib.dll.config and check if the default configuration matches your system environment:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <appSettings>

    <add key="MSBuildPath"

      value="c:\Windows\Microsoft.NET\Framework\v4.0.30319\" />

    <add key="FSIPath"

      value="[ProgramFiles]\FSharp-2.0.0.0\bin\" />

  </appSettings>

</configuration>

If you run the build.bat from the command line then your first FAKE script (build.fsx) will be executed. If everything works fine you will get the following output:

Hello World from FAKE

Now open the build.fsx with Visual Studio. It should look like this:

// include Fake libs

#I @"tools\FAKE"

#r "FakeLib.dll"

open Fake

 

// Default target

Target? Default <-

    fun _ –> trace "Hello World from FAKE"

 

// start build

Run? Default

As you can see the code is really simple. The very first lines include the FAKE libraries and are vital in all FAKE build scripts.

After this header the Default target is defined. A target definition contains of two important parts. The first is the name of the target (here “Default”) and the second is an action (here a simple trace of “Hello world”). Action can be defined as lambda expressions or methods.

The last line runs the “Default” target – which means it executes the defined action.

Cleaning the last build output

A typical first step in nearly every build scenario is to clean the output of the last build. We can achieve this by modifying the build.fsx to the following:

// include Fake libs

#I @"tools\FAKE"

#r "FakeLib.dll"

open Fake

 

// Properties

let buildDir = @".\build\"

 

// Targets

Target? Clean <-

    fun _ -> CleanDir buildDir

 

Target? Default <-

    fun _ -> trace "Hello World from FAKE"

 

// Dependencies

For? Default <- Dependency? Clean

 

// start build

Run? Default

We introduced some new concepts in this snippet. At first we defined a global property called “buildDir” with the relative path of a temporary build folder.

In the Clean target we want the CleanDir task to clean up this build directory. This simply deletes all files in the folder or creates the directory if necessary.

In the dependencies section we say that the Default target is dependent of the Clean target. In other words Clean is a prerequisite of Default and will be run before the execution of Default:

Clean is a dependency of Default 

Building the application

In the next step we want to compile our application libraries, which means we want to compile all projects under /src/app with MSBuild.

// include Fake libs

#I @"tools\FAKE"

#r "FakeLib.dll"

open Fake

 

// Properties

let buildDir = @".\build\"

let appReferences = Scan (!+ @"src\app\**\*.csproj")

 

// Targets

Target? Clean <-

  fun _ -> CleanDir buildDir

 

Target? BuildApp <-

  fun _ ->

    let target = "Build"

    // compile all projects below src/app/ in Release mode

    let apps = MSBuildRelease buildDir target appReferences

    // log the output files

    Log "AppBuild-Output: " apps

 

Target? Default <-

  fun _ -> trace "Hello World from FAKE"

 

// Dependencies

For? BuildApp <- Dependency? Clean

For? Default <- Dependency? BuildApp

 

// start build

Run? Default

Again, we defined a new build target named “BuildApp” which compiles all project files given in the property appReferences with the MSBuild task. The output will be copied to buildDir.

In order to find the right project files “FAKE – F# Make” scans the folder src/app/ and all subfolders with the given pattern. Therefore a similar FileSet definition like in NAnt or MSBuild (see project page for details) is used.

In addition the target dependencies are modified. Now Default is dependent of BuildApp and BuildApp needs Clean as a prerequisite.

This means the execution order is: Clean ==> BuildApp ==> Default.

App-Build output 

Building Test projects

Now our main application will be built automatically and it’s time to build the test application. We use pretty the same concepts as before:

// include Fake libs

#I @"tools\FAKE"

#r "FakeLib.dll"

open Fake

 

// Properties

let buildDir = @".\build\"

let testDir  = @".\test\"

let appReferences  = !+ @"src\app\**\*.csproj" |> Scan

let testReferences = !+ @"src\test\**\*.csproj" |> Scan

 

// Targets

Target? Clean <-

  fun _ –>

    CleanDir buildDir

    CleanDir testDir

 

Target? BuildApp <-

  fun _ ->

    let target = "Build"

    // compile all projects below src/app/ in Release mode

    let apps = MSBuildRelease buildDir target appReferences

    // log the output files

    Log "AppBuild-Output: " apps

 

 

Target? BuildTest <-

  fun _ ->

    let testApps = MSBuildDebug testDir "Build" testReferences

    Log "TestBuild-Output: " testApps  

 

Target? Default <-

  fun _ -> trace "Hello World from FAKE"

 

// Dependencies

For? BuildApp <- Dependency? Clean

For? BuildTest <- Dependency? Clean

For? Default <-

    Dependency? BuildApp

      |> And? BuildTest

 

// start build

Run? Default

This time we define a new target “BuildTest”, which compiles all C# projects below src/test/ in Debug mode and we mark the target as a dependency of Default.

As you can see the Clean target is now a dependency of BuildApp and BuildTest, since we also have to clean the test dir. Although the Clean target is referenced twice the “FAKE – F# Make” runtime ensures that it will only be executed once.

The execution order is now Clean ==> BuildApp ==> BuildTest ==> Default.

Testing the test assemblies with NUnit

Now all our projects will be compiled and we can use the NUnit task in order to test our test assemblies:

// include Fake libs

#I @"tools\FAKE"

#r "FakeLib.dll"

open Fake

 

// Properties

let buildDir = @".\build\"

let testDir  = @".\test\"

let appReferences  = !+ @"src\app\**\*.csproj" |> Scan

let testReferences = !+ @"src\test\**\*.csproj" |> Scan

let testAssemblies = !+ (testDir + @"\NUnit.Test.*.dll") |> Scan

let nunitPath = @".\Tools\NUnit\bin"

let nunitOutput = testDir + @"TestResults.xml"

 

// Targets

Target? Clean <-

  fun _ ->

    CleanDir buildDir

    CleanDir testDir

 

Target? BuildApp <-

  fun _ ->

    let target = "Build"

    // compile all projects below src/app/ in Release mode

    let apps = MSBuildRelease buildDir target appReferences

    // log the output files

    Log "AppBuild-Output: " apps

 

 

Target? BuildTest <-

  fun _ ->

    let testApps = MSBuildDebug testDir "Build" testReferences

    Log "TestBuild-Output: " testApps  

 

 

Target? Test <-

  fun _ ->

    NUnit (fun p ->

        {p with

           ToolPath = nunitPath;

           DisableShadowCopy = true;

           OutputFile = nunitOutput})

      testAssemblies

 

 

Target? Default <-

  fun _ -> trace "Hello World from FAKE"

 

// Dependencies

For? BuildApp <- Dependency? Clean

For? BuildTest <- Dependency? Clean

For? Test <-

    Dependency? BuildApp

      |> And? BuildTest

For? Default <- Dependency? Test   

 

// start build

Run? Default

Our new target “Test” scans the test directory for test assemblies and runs them with NUnit. The mysterious part (fun p –> …) simply overrides the default parameters of the NUnit task and allows to specify concrete parameters.

The execution order is now Clean ==> BuildApp ==> BuildTest ==> Test ==> Default.

Test output

Deploying a zip file

Now we want to deploy a *.zip file containing our application:

// include Fake libs

#I @"tools\FAKE"

#r "FakeLib.dll"

open Fake

 

// Properties

let buildDir = @".\build\"

let testDir  = @".\test\"

let deployDir  = @".\deploy\"

let appReferences  = !+ @"src\app\**\*.csproj" |> Scan

let testReferences = !+ @"src\test\**\*.csproj" |> Scan

let testAssemblies = !+ (testDir + @"\NUnit.Test.*.dll") |> Scan

let nunitPath = @".\Tools\NUnit\bin"

let nunitOutput = testDir + @"TestResults.xml"

let filesToZip = !+ (buildDir + "\**\*.*") — "*.zip" |> Scan

let zipFileName = deployDir + "Calculator.zip"

 

// Targets

Target? Clean <-

  fun _ -> CleanDirs [buildDir; testDir; deployDir]

 

Target? BuildApp <-

  fun _ ->

    let target = "Build"

    // compile all projects below src/app/ in Release mode

    let apps = MSBuildRelease buildDir target appReferences

    // log the output files

    Log "AppBuild-Output: " apps

 

 

Target? BuildTest <-

  fun _ ->

    let testApps = MSBuildDebug testDir "Build" testReferences

    Log "TestBuild-Output: " testApps  

 

 

Target? Test <-

  fun _ ->

    NUnit (fun p ->

        {p with

           ToolPath = nunitPath;

           DisableShadowCopy = true;

           OutputFile = nunitOutput})

      testAssemblies

 

Target? Deploy <-

  fun _ -> Zip buildDir zipFileName filesToZip

 

Target? Default <- DoNothing

 

// Dependencies

For? BuildApp <- Dependency? Clean

For? BuildTest <- Dependency? Clean

For? Test <-

    Dependency? BuildApp

      |> And? BuildTest

For? Deploy <- Dependency? Test

For? Default <- Dependency? Deploy

 

// start build

Run? Default

The new target “Deploy” scans the build directory for all files but zip-files. The result will be zipped to \deploy\Calculator.zip via the Zip task.

The execution order is now Clean ==> BuildApp ==> BuildTest ==> Test ==> Deploy ==> Default whereas Default now is only used as a entry point.

What’s next?

Now your build file should look like completeBuild.fsx and you are ready to write your own “FAKE – F# Make” build scripts. If you have any questions or suggestions feel free to comment on this post.

In the next article I will show how we can add FxCop to our build in order to check specific naming rules.

Tags: , , , , ,

Thursday, 23. October 2008


Using PLINQ in F# – Parallel Map and Reduce (Fold) functions – part 1

Filed under: .NET 3.0,C#,F# — Steffen Forkmann at 18:25 Uhr

If your wondering how Google computes query results in such a short time you have to read the famous “MapReduce”-Paper by Jeffrey Dean and Sanjay Ghemawat (2004). It shows how one can split large tasks into a mapping and a reduce step which could then be processed in parallel.

With PLINQ (part of the Parallel Extensions to the .NET Framework) you can easily use “MapReduce”-pattern in .NET and especially F#. PLINQ will take care of all the MultiThreading and load balancing stuff. You only have to give PLINQ a map and a reduce (or fold) function.

Lets consider a small example. Someone wants to compute the sum of the factorials of all integers from 1 to 3000. With List.map and List.fold_left this is a very easy task in F#:

#light
open System

let add a b = a + b
let fac (x:bigint) = [1I..x] |> List.fold_left (*) 1I

let sum =
  [1I..3000I]
    |> List.map fac
    |> List.fold_left add 0I

printfn "Sum of Factorials: %A" sum

Of course you could do much much better if you don’t compute every factorial on its own (I will show this in one of the next parts) – but for this time I need an easy function that is time consuming.

This simple Task needs 27 sec. on my Core 2 Duo E6550 with 2.33 GHz and 3.5 GB RAM.

But we can do better if we use parallel map and fold functions with help of PLINQ:

let pMap (mapF:'a -> 'b) (data:IParallelEnumerable<'a>) =
  ParallelEnumerable.Select(data, mapF)

let pFold foldF seed (data:IParallelEnumerable<'a>)=
  ParallelEnumerable.Aggregate<'a,'b>(
    data, seed, new Func<'b,'a,'b>(foldF))

Now we can easily transform our calculation to a parallel version:

let sum =
  [1I..3000I].AsParallel<bigint>()
    |> pMap fac 
    |> pFold add 0I

Putting all together we can write a small test application:

#light 
open System
open System.Linq
open System.Diagnostics

let testRuntime f =
  let watch = new Stopwatch()
  watch.Start()
  (f(),watch.Elapsed)

let add a b = a + b
let fac (x:bigint) = [1I..x] |> List.fold_left (*) 1I

let list = [1I..3000I]

let pMap (mapF:'a -> 'b) (data:IParallelEnumerable<'a>)=
  ParallelEnumerable.Select(data, mapF)

let pFold foldF seed (data:IParallelEnumerable<'a>)=
  ParallelEnumerable.Aggregate<'a,'b>(
    data, seed, new Func<'b,'a,'b>(foldF))

let PLINQ() =
  list.AsParallel<bigint>()
    |> pMap fac
    |> pFold add 0I

let sequential() =
  list
   |> List.map fac
   |> List.fold_left add 0I

let (sumSequential,timeSequential) =
  testRuntime sequential
printfn "Time Normal: %.3fs" timeSequential.TotalSeconds

let (sumPLINQ,timePLINQ) =
  testRuntime PLINQ
printfn "Time PLINQ: %.3fs" timePLINQ.TotalSeconds

timePLINQ.TotalSeconds / timeSequential.TotalSeconds
  |> printfn "Ratio: %.2f"

sumSequential = sumPLINQ
  |> printfn "Same Results: %A"

On my machine I get the following results:

Time Normal: 27.955s

Time PLINQ: 15.505s

Ratio: 0.55

Same Results: true

This means I get nearly a perfect load balancing on my two processors for this task.

In part II I describe how one can compute a series of functions in parallel.

Tags: , , , , , , ,