![]() |
|
Spaces home Inside F#ProfileFriendsBlogMore ![]() | ![]() |
|
|
May 12 Connected component labeling in F#Kirill recently posted a blog about connected component labeling in C#. He also asked for solutions in other languages, so of course I had to code it in F#. You can read his blog for more info, but the gist is, given a black and white grid, do a "flood fill" of each section with a different random color. Here's a sample before-and-after screenshot: There are slider controls which let you control both the size of the initial black-and-white grid and the "white percentage". So here's a bigger example that starts off mostly white: When you move the sliders you get a new black-and-white grid, and then when you click the button, it colors it. Get the idea? Cool and fun. Anyway, I shamelessly stole all Kirill's UI code, transliterated it to F#, and then implemented the Union-Find algorithm Kirill had mentioned, and it seems to be blazingly fast. So I present for your enjoyment, without further commentary, the F# code: #light #r "C:\WINDOWS\assembly\GAC_MSIL\System.Core\3.5.0.0__b77a5c561934e089\System.Core.dll" open System // A partition is a mutable set of values, where one arbitrary value in the set // is chosen as the canonical representative for that set. type Partition<'a>(orig : 'a) as this = [<DefaultValue(false)>] val mutable parent : Partition<'a> [<DefaultValue(false)>] val mutable rank : int let rec Find(x : Partition<'a>) = if Object.ReferenceEquals(x.parent, x) then x else x.parent <- Find(x.parent) x.parent do this.parent <- this // The representative element in this partition member this.Find() = Find(this) // Merges two partitions member this.Union(other : Partition<'a>) = let thisRoot = this.Find() let otherRoot = other.Find() if thisRoot.rank < otherRoot.rank then otherRoot.parent <- thisRoot elif thisRoot.rank > otherRoot.rank then thisRoot.parent <- otherRoot elif not (Object.ReferenceEquals(thisRoot, otherRoot)) then otherRoot.parent <- thisRoot thisRoot.rank <- thisRoot.rank + 1 // The original value of this element member this.Value = orig open System.Diagnostics open System.Windows.Forms open System.Drawing let random = new Random() type Info() = let mutable iMax = 1 let mutable jMax = 1 // The original grid (true = white) let mutable grid = Array2.create iMax jMax true // Connected components let mutable colorField = Array2.create iMax jMax (new Partition<_>(Color.White)) // Initialize() resets the data and returns a white/black array member this.Initialize pctWhite size = iMax <- size jMax <- size grid <- Array2.init iMax jMax (fun _ _ -> float (random.Next(100)) < 100.0 * pctWhite) colorField <- Array2.init iMax jMax (fun _ _ -> new Partition<_>(Color.FromArgb(random.Next(256), random.Next(256), random.Next(256)))) Array2.init iMax jMax (fun i j -> if grid.[i,j] then Color.White else Color.Black) // Connect() connects components, and returns a tuple (numConnectedComponents, newColorArray) member this.Connect() = // connect components... for i in 0 .. iMax-1 do for j in 0 .. jMax-1 do if i <> 0 then if grid.[i-1,j] = grid.[i,j] then colorField.[i-1,j].Union(colorField.[i,j]) if j <> 0 then if grid.[i,j-1] = grid.[i,j] then colorField.[i,j-1].Union(colorField.[i,j]) if i <> iMax-1 then if grid.[i+1,j] = grid.[i,j] then colorField.[i+1,j].Union(colorField.[i,j]) if j <> jMax-1 then if grid.[i,j+1] = grid.[i,j] then colorField.[i,j+1].Union(colorField.[i,j]) // ... count how many there are, and pick a color for each component let h = new System.Collections.Generic.HashSet<_>() let theField = Array2.init iMax jMax (fun i j -> let rep = colorField.[i,j].Find() h.Add(rep) |> ignore rep.Value // color of representative element ) (h.Count, theField) // the UI type Form1() as this = inherit Form() as base let Drawing = new PictureBox(Anchor = (AnchorStyles.Top ||| AnchorStyles.Bottom ||| AnchorStyles.Left ||| AnchorStyles.Right), Location = new System.Drawing.Point(12, 12), Name = "Drawing", Size = new System.Drawing.Size(485, 405), TabIndex = 0, TabStop = false) let FindComponents = new Button(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Right), Location = new System.Drawing.Point(358, 538), Name = "FindComponents", Size = new System.Drawing.Size(139, 37), TabIndex = 2, Text = "Find components", UseVisualStyleBackColor = true) let PercentageSlider = new TrackBar(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left ||| AnchorStyles.Right), LargeChange = 2, Location = new System.Drawing.Point(176, 423), Maximum = 40, Name = "PercentageSlider", Size = new System.Drawing.Size(321, 53), TabIndex = 3, Value = 20) let label1 = new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), AutoSize = true, Location = new System.Drawing.Point(12, 434), Name = "label1", Size = new System.Drawing.Size(124, 17), TabIndex = 4, Text = "White percentage:") let label2 = new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), AutoSize = true, Location = new System.Drawing.Point(12, 470), Name = "label2", Size = new System.Drawing.Size(71, 17), TabIndex = 6, Text = "Field size:") let FieldSizeSlider = new TrackBar(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left ||| AnchorStyles.Right), LargeChange = 2, Location = new System.Drawing.Point(176, 470), Maximum = 100, Minimum = 1, Name = "FieldSizeSlider", Size = new System.Drawing.Size(321, 53), TabIndex = 5, Value = 5) let Status = new Label(Anchor = (AnchorStyles.Bottom ||| AnchorStyles.Left), AutoSize = true, Location = new System.Drawing.Point(15, 538), Name = "Status", Size = new System.Drawing.Size(0, 17), TabIndex = 7) let mutable field = Array2.create 1 1 Color.White // the array we will Draw let Draw (canvas : Control) (graphics : Graphics) = let width = float32 canvas.ClientSize.Width let height = float32 canvas.ClientSize.Height let iMax = Array2.length1 field let jMax = Array2.length2 field let iMaxFloat = float32 iMax let jMaxFloat = float32 jMax for i in 0 .. iMax-1 do for j in 0 .. jMax-1 do let w = width / iMaxFloat let h = height / jMaxFloat use brush = new SolidBrush(field.[i, j]) graphics.FillRectangle(brush, w * float32 i, h * float32 j, w, h) let info = new Info() do this.InitializeComponent() member this.Repaint() = Drawing.Invalidate() member private this.Form1_Resize sender e = let maxFieldSize = Math.Max(5, Math.Min(Drawing.ClientSize.Width, Drawing.ClientSize.Height)) FieldSizeSlider.Maximum <- maxFieldSize this.Repaint() member private this.FindComponents_Click sender e = let stopwatch = new Stopwatch() stopwatch.Start() let count, newField = info.Connect() field <- newField stopwatch.Stop() Status.Text <- sprintf "Found %d connected components, took %dms" count stopwatch.ElapsedMilliseconds this.Repaint() member this.Regenerate() = let size = FieldSizeSlider.Value let pct = float PercentageSlider.Value / float PercentageSlider.Maximum field <- info.Initialize pct size Status.Text <- sprintf "%d x %d, White:%f" size size pct this.Repaint() member private this.InitializeComponent() = (Drawing :> System.ComponentModel.ISupportInitialize).BeginInit() (PercentageSlider :> System.ComponentModel.ISupportInitialize).BeginInit() (FieldSizeSlider :> System.ComponentModel.ISupportInitialize).BeginInit() this.SuspendLayout() Drawing.Paint.AddHandler(fun s e -> Draw (Drawing :> Control) e.Graphics) FindComponents.Click.AddHandler(new EventHandler(this.FindComponents_Click)) PercentageSlider.Scroll.AddHandler(fun s e -> this.Regenerate()) FieldSizeSlider.Scroll.AddHandler(fun s e -> this.Regenerate()) this.AcceptButton <- FindComponents this.AutoScaleDimensions <- new System.Drawing.SizeF(float32 8, float32 16) this.AutoScaleMode <- System.Windows.Forms.AutoScaleMode.Font this.ClientSize <- new System.Drawing.Size(509, 587) this.Controls.Add(Status) this.Controls.Add(label2) this.Controls.Add(FieldSizeSlider) this.Controls.Add(label1) this.Controls.Add(PercentageSlider) this.Controls.Add(FindComponents) this.Controls.Add(Drawing) this.DoubleBuffered <- true this.Name <- "Form1" this.StartPosition <- System.Windows.Forms.FormStartPosition.CenterScreen this.Text <- "Find connected components" this.Resize.AddHandler(new System.EventHandler(this.Form1_Resize)) (Drawing :> System.ComponentModel.ISupportInitialize).EndInit() (PercentageSlider :> System.ComponentModel.ISupportInitialize).EndInit() (FieldSizeSlider :> System.ComponentModel.ISupportInitialize).EndInit() this.ResumeLayout(false) this.PerformLayout() this.Regenerate() [<STAThread>] do Application.EnableVisualStyles() Application.SetCompatibleTextRenderingDefault(false) Application.Run(new Form1()) May 09 "FSI is the new perl"When I need a quick-and-dirty fifteen-line script for a one-off task involving text files, I often use perl. The problem is, I use perl infrequently enough that each time I decide to use it, I usually need to give myself a five-minute refresher on the syntax and common functions before I can get the job done. On the other hand, I use F# every day. Thus today's blog entry is just to call out how easy it is to write little handy scripts with F#. FSII haven't talked about FSI much. You can use F# Interactive from the command-line (fsi.exe), or as a Visual Studio Add-in (inside VS, select "Tools\Add-in Manager..." and click the check box next to F# interactive). I like the VS add-in because it makes it easy to use the VS text editor as a "scratch pad" with syntax highlighting and intellisense. When you have code you want to execute, just select it and press Alt-Enter and the code will be sent to the FSI window and executed. A sample scriptSuppose I want to know what percentage of the sample .fs files in the F# distribution use the lightweight syntax option (#light). I can quickly hack together: #light open System.IO let Average l = List.reduce_left (+) l / List.length l let rec AllFiles dir = seq { yield! Directory.GetFiles(dir) for subdir in Directory.GetDirectories(dir) do yield! AllFiles subdir } [ for file in AllFiles @"C:\Program Files\FSharp-1.9.4.15\samples" do if file.EndsWith(".fs") then if File.ReadAllText(file).Contains("#light") then yield 100 else yield 0 ] |> Average |> printfn "%d%% of files use #light" Highlight this code, press alt-enter, and FSI says val Average : int list -> int val AllFiles : string -> seq<string> 91% of files use #light Cool. I wonder which files don't use #light? I can just add a little code to the "else" branch: else do printfn "no #light: %s" file yield 0 ] and then highlight the last block of code (from "[for..." to the end of the file) and hit alt-enter again, and I see no #light: C:\Program Files\FSharp-1.9.4.15\samples\fsharp\Differentiate\lex.fs no #light: C:\Program Files\FSharp-1.9.4.15\samples\fsharp\Differentiate\pars.fs no #light: C:\Program Files\FSharp-1.9.4.15\samples\fsharp\FLinq\dumper.fs no #light: C:\Program Files\FSharp-1.9.4.15\samples\fsharp\FLinq\program.fs no #light: C:\Program Files\FSharp-1.9.4.15\samples\fsharp\Parsing\ast.fs 91% of files use #light val it : unit = () in the FSI window. Neat. There might be shorter or more elegant ways to accomplish the same goal with F# (or perl, or what have you), but my focus today is on writability, demonstrating that it's easy to hack little scripts like this in less than five minutes. Some F# detailsThe script above demos some F# bits that I don't think I've talked about before. The "seq { ... }" code is an example of a sequence comprehension, another example of F#'s computation expression syntax. Recall that "seq" (short for "sequence") just means "IEnumerable". Inside a sequence comprehension, "yield" means the same thing as it does in a C# iterator block, and "yield!" means nearly the same thing, except that you pass it a sequence rather than a single item. (In other words, "yield! s" is like "for i in s do yield i", where s is a seq<'a> and i is an 'a.) So AllFiles returns a seq<string> that recursively enumerates all the file names under some directory. The portion in square brackets is a list comprehension, which is pretty much just like a sequence comprehension, except the result is a list<'a> rather than a seq<'a>. Thus we end up with a list of integers, with the value 100 for each file containing "#light", and 0 for the other files. We average the values in the list, and we get the percentage of files that use #light. That's all for today - just some quickie fun I wanted to share! May 07 "An Introduction to Async Workflows in F#", or "how to utilize all those CPUs without writing lots of threading code", part threeLast time I showed you how to use the F# Control library to easily parallelize a primes-computing program. Today I'll show you how to use the same library in C# to achieve the same end. I'll also talk more about one of the real strong suits of the library - making async programming more compositional - and this leads to all kinds of interesting discussions for C#, involving LINQ, as well as iterators and "yield". Hold on tight - it's a wild ride! Easy async from C#As we saw last time, the F# solution involved calling library functions like Async.Parallel, but it also involved a tiny async workflow that looked like this: async { return (x, IsPrime x) } That is, this part of the F# solution leveraged the F# computation expression syntax. We don't have this syntax available in C#, so what are we to do? As I hinted obliquely last time, C# has its own syntax sugar that will fit the bill: LINQ. The key piece of the original C# code that we need to parallelize can be written this way: var primeInfo = nums.Select((x) => new KeyValuePair<int, bool>(x, IsPrime(x))).ToArray(); That's the bit that takes more than 12 seconds to run on my box. We can parallelize just as in F#, using the F# library + LINQ thusly: var computations = nums.Select((x) => from dummy in AsyncExtensions.StartWorkflow // enter async monad & protect rest of code in a lambda select new KeyValuePair<int, bool>(x, IsPrime(x))); var primeInfo = AsyncExtensions.Run(AsyncExtensions.Parallel(computations)); I'll explain it in a minute, but the first thing to note is - we got the same big win as in F#. If you look back at the original C# code from part one, you'll see it was about 10 lines of mess involving ThreadPool.QueueUserWorkItem, ManualResetEvent, and Interlocked.Decrement. The code above is much simpler - we just create a sequence of all the computations we want to do, parallelize them, and run. AsyncExtensions is a small wrapper class I authored that just wraps up the F# Control library in a thin facade that makes it more C#-friendly. You'll get a chance to see the implementation shortly. The Run() and Parallel() methods just forward calls to the corresponding Async calls in the F# Control library. The interesting/unexpected part is the LINQ query. Though you typically think of LINQ as just syntactic sugar for authoring queries over IEnumerables, the way LINQ is defined in the C# language specification is far more general, and enables the LINQ syntax (things like "from" and "select") to be used in an arbitrary monad. (I demonstrated this a long while back, when I showed how to use LINQ in the definition of monadic parser combinators in C#.) I'm continuing to defer the full discussion about monads (what they are, why they matter, what LINQ has to do with it) because at this point it would still be a needless distraction. All you need to do right now is take it on faith that this C# code: from dummy in AsyncExtensions.StartWorkflow rest_of_linq_query means the same thing as this F# code: async { body_of_computation } and that this C# inside a LINQ query: select expr means the same as this F# inside a computation expression: return expr and you're good to go. Actually, you don't have to just take it on faith - you can try the code yourself. Here's the full C# code. Just throw it in a C# project, reference FSharp.Core.dll from your F# 1.9.4.15 installation, and try it out. But then keep reading - there's more blog after this code: //#define SYNC using System; using System.Collections.Generic; using System.Linq; using Microsoft.FSharp.Core; using Microsoft.FSharp.Control; using System.Diagnostics; class Program { static Stopwatch stopWatch = new Stopwatch(); static void ResetStopWatch() { stopWatch.Reset(); stopWatch.Start(); } static void ShowTime() { Console.WriteLine("took {0} ms", stopWatch.ElapsedMilliseconds); } static bool IsPrime(int x) { for (int i = 2; i < x; ++i) { if (x % i == 0) return false; } return true; } public static void Main() { var nums = new int[4001]; for (int i = 0; i < nums.Length; ++i) nums[i] = 10000000 + i; ResetStopWatch(); #if SYNC Console.WriteLine("Computing primes sequentially..."); var primeInfo = nums.Select((x) => new KeyValuePair<int, bool>(x, IsPrime(x))).ToArray(); #else Console.WriteLine("Computing primes in parallel..."); var computations = nums.Select((x) => from dummy in AsyncExtensions.StartWorkflow select new KeyValuePair<int, bool>(x, IsPrime(x))); var primeInfo = AsyncExtensions.Run(AsyncExtensions.Parallel(computations)); #endif ShowTime(); var primes = from x in primeInfo where x.Value select x.Key; foreach (var x in primes) Console.Write("{0},", x); Console.WriteLine(); ShowTime(); Console.WriteLine("press a key"); Console.ReadKey(); } } // boilerplate code to wrap F# library in a nice C# facade static class AsyncExtensions { // easily massage "Func" types into F# function types public static FastFunc<A, B> ToFastFunc<A, B>(this Func<A, B> f) { return FuncConvert.ToFastFunc(new Converter<A, B>(f)); } // LINQ syntax sugars public static Async<B> Select<A, B>(this Async<A> x, Func<A, B> selector) { return AsyncModule.async.Bind(x, ToFastFunc<A, Async<B>>((r) => AsyncModule.async.Return(selector(r)))); } public static Async<V> SelectMany<T, U, V>( this Async<T> p, Func<T, Async<U>> selector, Func<T, U, V> projector) { return AsyncModule.async.Bind(p, ToFastFunc<T, Async<V>>(r1 => AsyncModule.async.Bind(selector(r1), ToFastFunc<U, Async<V>>(r2 => AsyncModule.async.Return(projector(r1, r2)))))); } // Wrap F# Control library functions in simpler facade public static Async<R[]> Parallel<R>(IEnumerable<Async<R>> computations) { return Async<int>.Parallel<IEnumerable<Async<R>>, R>(computations); } public static R Run<R>(Async<R> computation) { return Async<int>.Run(computation, Option<AsyncGroup>.None, Option<int>.None, Option<bool>.None); } // convenience object to get in the Async monad public static Async<int> StartWorkflow = AsyncModule.async.Return(0); } So there's some fun C# code you can run with today. Compositional async codeWe've seen how the F# Control library can be used to parallelize computations in both F# and C#. But parallelization is just one aspect of the F# Control library. An even more enticing value proposition is the ability to write async code that composes easily. Let's consider another example, from a networking domain, using WCF. Suppose I have a web service that exposes an endpoint that can be used to compute squares of integers - for example, you pass 5 to the web service, and it does some extremely difficult computations and finally returns you the answer 25. (As is often the case, my blog samples are very contrived, in order to stay simple.) Given a particular object that represents the connection to this service, we might write come C# code like this: static int SumSquares(IMyClientContract client) { ((IClientChannel)client).Open(); var sq1 = client.Square(3); var sq2 = client.Square(4); ((IClientChannel)client).Close(); return sq1 + sq2; } as a sample of what we can do with a "client" connection to the web service. Now, each of the operations on the client (Open(), Square(), and Close()) can potentially involve a call out over the network. In the code above, all the code executes synchronously, which means this function holds a CLR thread for the duration of the whole call to SumSquares(). Sometimes this is fine, but often (especially when writing servers or middle-tier components) we need to be more frugal when it comes to resources like threads, and ensure that we are only holding threads when necessary - not while we are blocked, waiting for some network call to return. To this end, the .NET framework provides the Begin/End pattern and IAsyncResult type. So for example, in addition to methods like Open(), frameworks also provide the corresponding async versions via BeginOpen() and EndOpen(). This API pattern makes it possible to get the job done - you can make a "Begin" call with a callback object, release the current thread, and then when the operation eventually finishes, your callback gets invoked with the result so you can pick up where you left off (probably with code now running on a different thread). While the Begin/End pattern is somewhat workable for a single call, it fails miserably for composing a series of async calls. Suppose we want to author BeginSumSquares() and EndSumSquares(), with the implementation making calls to the Begin/End versions of Open, Square, and Close. This is a perfectly reasonable thing to want to do - in fact, if you work on framework code in this kind of domain, this may be something you need to do all the time. Ideally it would be straightforward - SumSquares is a five-line method, and we just want to do the same thing, only async. In practice, it's a nightmare. I won't even attempt to write the code for Begin/End-SumSquares here, because I know I will get it wrong. The Begin/End pattern forces you into a hideous mess of spaghetti code where each async call necessitates a new callback method and a new IAsyncResult object, and this simple SumSquares example will probably take on the order of 100 lines of code to implement async. If you've never had to write this kind of code before, thank your lucky stars. Again, the problem is that the Begin/End pattern does not compose. If I have two synchronous methods I want to call in series, one after another, I just write "Method1(); Method2();". But with two async methods, I have to write tons of code just to correctly string two calls together in series. What we need is a pattern for writing async code that makes composing a series of async calls as easy as composing a series of sync calls. In F# this is easy to do. The F# synchronous code looks like this: let SumSquares (client : IMyClientContract) = (box client :?> IClientChannel).Open() let sq1 = client.Square(3) let sq2 = client.Square(4) (box client :?> IClientChannel).Close() sq1 + sq2 (The "box" and ":?>" bits are just how the cast to type IClientChannel is performed in F#.) To make it async, rather than use the Begin/End pattern, we can use the F# Async pattern (which is easy to build on top of the Begin/End methods, using the Async.BuildPrimitive function in the library). Then we can use F# async workflows like this: let SumSquaresAsync (client : IMyClientContract) = async { do! (box client :?> IClientChannel).OpenAsync() let! sq1 = client.SquareAsync(3) let! sq2 = client.SquareAsync(4) do! (box client :?> IClientChannel).CloseAsync() return sq1 + sq2 } The "do!" keyword in an F# async workflow lets us call an async method when we don't care about the result, and the "let!" keyword calls an async method and binds the result to a new variable name. As a result, our original five-line synchronous method is still just five lines when we rewrite it to run asynchronously - the difference is that we write the code inside an async workflow. It turns out that we can do the same thing for C#. This sync C# code: static int SumSquares(IMyClientContract client) { ((IClientChannel)client).Open(); var sq1 = client.Square(3); var sq2 = client.Square(4); ((IClientChannel)client).Close(); return sq1 + sq2; } can be rewritten as this async code: static Async<int> SumSquaresAsync(IMyClientContract client) { return from _0 in AsyncExtensions.StartWorkflow from _1 in ((IClientChannel)client).OpenAsync() from sq1 in client.SquareAsync(3) from sq2 in client.SquareAsync(4) from _2 in ((IClientChannel)client).CloseAsync() select sq1 + sq2; } using the F# Control library from C#. Again, I am using LINQ. Once we enter the async monad (the first line with "StartWorkflow"), each subsequent "from" in C# is like a "let!" in F#. There is no LINQ syntax that corresponds to "do!", so we have to use "from" and bind the meaningless results to dummy variable names (I chose "_0", "_1", and "_2" as the names for my "don't care" variables here). The code looks a little weird, since we are "abusing" LINQ in order to achieve our goal. But I am a pragmatic, and if I can save myself from having to write 100 lines of nightmare Begin/End/IAsyncResult/callback code in order to achieve composable async C# code, then I am willing to pay the price of having to use some awkward syntax (the "from", "select", and dummy variables). The code looks a little weird, but it still feels like a big win. Shortcomings of the LINQ approach to async in C#, and an alternative approachThe async C# code I just showed is pretty nifty, but there are more problems with the "LINQ approach to async" other than just the awkward syntax. The example I chose (SumSquaresAsync) was very simple - a method with straight-line code that made five async method calls. What if we want to start with some more complicated synchronous code, that involves if-then-else, while loops, try-catch blocks, or arbitrary other C# constructs? These constructs do not have an obvious/straightforward mapping into LINQ when we try to create the corresponding async code. As a result, the async version of code using such constructs will probably have to look different from (and be more complicated than) the corresponding sync code. That's unfortunate, as it erodes the main benefit we were out to achieve in the first place (async code that's as simple as the original sync code). There is an alternative to the LINQ approach. Another of the C# language's "syntax sugars" fits the bill: "yield". Iterator blocks that use the "yield" statement create a way to write C# code that will 'exit and return later where we left off', which is just the type of thing you need for writing async code. As a result, it's possible to build an async library atop the iterator metaphor, rather than atop an async monad (utilizing LINQ). This is the approach taken, for example, by CCR in Robotics Studio, which you can read a little about here. I haven't yet studied this approach in depth, but it seems very interesting, as it doesn't suffer the LINQ drawbacks I just mentioned in the previous paragraph. It's possible the two approaches might be complimentary (perhaps the same library can expose both the LINQ programming model and the "yield"/iterator programming model for composing async computations). What's next?There's potentially a lot more to talk about, but this is a good place to wrap things up for today. Source codeBelow is the full source code for the WCF example - first in F#, then in C#. F# code #light #r "C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\System.Runtime.Serialization.dll" #r "C:\WINDOWS\Microsoft.NET\Framework\v3.0\Windows Communication Foundation\System.ServiceModel.dll" open System open System.Collections.Generic open System.Diagnostics open System.ServiceModel open System.Threading open System.ServiceModel.Channels open Microsoft.FSharp.Control // define WCF service [<ServiceContract>] type IMyContract = interface [<OperationContract>] abstract Square: x:int -> int end [<ServiceContract(Name="IMyContract")>] type IMyClientContract = interface [<OperationContract>] abstract Square: x:int -> int [<OperationContract(AsyncPattern = true)>] abstract BeginSquare: x:int * cb:AsyncCallback * o:obj -> IAsyncResult abstract EndSquare: iar:IAsyncResult -> int end type MyService() = class interface IMyContract with member this.Square x = x * x end // set up a WCF service, and then run a client function against it let DoWCFRun (clientFunc : IMyClientContract -> int) = let addr = "http://localhost/WCF" let address = new Uri(addr) let host = new ServiceHost(typeof<MyService>, [|address|]) let reliableBinding = new WSHttpBinding(SecurityMode.None, true) host.AddServiceEndpoint(typeof<IMyContract>, reliableBinding, "") |> ignore host.Open() let cf = new ChannelFactory<IMyClientContract>(reliableBinding, new EndpointAddress(addr)) let client = cf.CreateChannel() printfn "about to call client" let ans = clientFunc client printfn "done - answer is %d" ans host.Close() // a sample client function that runs synchronously let SumSquares (client : IMyClientContract) = (box client :?> IClientChannel).Open() let sq1 = client.Square(3) let sq2 = client.Square(4) (box client :?> IClientChannel).Close() sq1 + sq2 // run it DoWCFRun SumSquares // define Async versions of the key client methods type IClientChannel with member this.OpenAsync() = Async.BuildPrimitive(this.BeginOpen, this.EndOpen) member this.CloseAsync() = Async.BuildPrimitive(this.BeginClose, this.EndClose) type IMyClientContract with member this.SquareAsync x = Async.BuildPrimitive(x, this.BeginSquare, this.EndSquare) // async version of our sample client - does not hold threads while calling out to network let SumSquaresAsync (client : IMyClientContract) = async { do! (box client :?> IClientChannel).OpenAsync() let! sq1 = client.SquareAsync(3) let! sq2 = client.SquareAsync(4) do! (box client :?> IClientChannel).CloseAsync() return sq1 + sq2 } DoWCFRun (SumSquaresAsync >> Async.Run) // ">>" is function composition operator printfn "press a key" Console.ReadKey() C# code //#define SYNC using System; using System.Collections.Generic; using System.Linq; using Microsoft.FSharp.Core; using Microsoft.FSharp.Control; using System.Diagnostics; using System.ServiceModel; using System.Threading; using System.ServiceModel.Channels; // define WCF service [ServiceContract] interface IMyContract { [OperationContract] int Square(int x); } [ServiceContract(Name = "IMyContract")] interface IMyClientContract { [OperationContract] int Square(int x); [OperationContract(AsyncPattern = true)] IAsyncResult BeginSquare(int x, AsyncCallback cb, object o); int EndSquare(IAsyncResult iar); } class MyService : IMyContract { public int Square(int x) { return x * x; } } class Program { // set up a WCF service, and then run a client against it public static void Main() { Uri address = new Uri("http://localhost/WCF"); ServiceHost host = new ServiceHost(typeof(MyService), address); Binding reliableBinding = new WSHttpBinding(SecurityMode.None, true); host.AddServiceEndpoint(typeof(IMyContract), reliableBinding, ""); host.Open(); ChannelFactory<IMyClientContract> cf = new ChannelFactory<IMyClientContract>(reliableBinding, new EndpointAddress(address)); IMyClientContract client = cf.CreateChannel(); Console.WriteLine("about to call client"); #if SYNC var ans = SumSquares(client); #else var ans = AsyncExtensions.Run(SumSquaresAsync(client)); #endif Console.WriteLine("done - answer is {0}", ans); host.Close(); Console.WriteLine("press a key"); Console.ReadKey(); } // a sample client function that runs synchronously static int SumSquares(IMyClientContract client) { ((IClientChannel)client).Open(); var sq1 = client.Square(3); var sq2 = client.Square(4); ((IClientChannel)client).Close(); return sq1 + sq2; } // async version of our sample client - does not hold threads while calling out to network static Async<int> SumSquaresAsync(IMyClientContract client) { return from _0 in AsyncExtensions.StartWorkflow from _1 in ((IClientChannel)client).OpenAsync() from sq1 in client.SquareAsync(3) from sq2 in client.SquareAsync(4) from _2 in ((IClientChannel)client).CloseAsync() select sq1 + sq2; } } // define Async versions of the key client methods static class ClientExtension { public static Async<Unit> OpenAsync(this IClientChannel client) { return AsyncExtensions.BuildVoidPrimitive(client.BeginOpen, client.EndOpen); } public static Async<Unit> CloseAsync(this IClientChannel client) { return AsyncExtensions.BuildVoidPrimitive(client.BeginClose, client.EndClose); } public static Async<int> SquareAsync(this IMyClientContract client, int x) { return AsyncExtensions.BuildPrimitive<int, int>(x, client.BeginSquare, client.EndSquare); } } // boilerplate code to wrap F# library in nice C# facade static class AsyncExtensions { public static FastFunc<A, B> ToFastFunc<A, B>(this Func<A, B> f) { return FuncConvert.ToFastFunc(new Converter<A, B>(f)); } public static FastFunc<Tuple<A, B>, C> ToTupledFastFunc<A, B, C>(this Func<A, B, C> f) { return FuncConvert.ToTupledFastFunc(new Converter<A, B, C>(f)); } public static FastFunc<Tuple<A, B, C>, D> ToTupledFastFunc<A, B, C, D>(this Func<A, B, C, D> f) { return FuncConvert.ToTupledFastFunc(new Converter<A, B, C, D>(f)); } public static Async<B> Select<A, B>(this Async<A> x, Func<A, B> selector) { return AsyncModule.async.Bind(x, ToFastFunc<A, Async<B>>( (r) => AsyncModule.async.Return(selector(r)))); } public static Async<V> SelectMany<T, U, V>(this Async<T> p, Func<T, Async<U>> selector, Func<T, U, V> projector) { return AsyncModule.async.Bind(p, ToFastFunc<T, Async<V>>(r1 => AsyncModule.async.Bind(selector(r1), ToFastFunc<U, Async<V>>(r2 => AsyncModule.async.Return(projector(r1, r2)))))); } public static Async<R[]> Parallel<R>(IEnumerable<Async<R>> computations) { return Async<int>.Parallel<IEnumerable<Async<R>>, R>(computations); } public static R Run<R>(Async<R> computation) { return Async<int>.Run(computation, Option<AsyncGroup>.None, Option<int>.None, Option<bool>.None); } public static Async<Unit> BuildVoidPrimitive(Func<AsyncCallback, object, IAsyncResult> begin, Action<IAsyncResult> end) { return Async<int>.BuildPrimitive(begin.ToTupledFastFunc(), FuncConvert.ToFastFunc(end)); } public static Async<R> BuildPrimitive<R>(Func<AsyncCallback, object, IAsyncResult> begin, Func<IAsyncResult, R> end) { return Async<int>.BuildPrimitive(begin.ToTupledFastFunc(), end.ToFastFunc()); } public static Async<R> BuildPrimitive<Arg, R>(Arg a, Func<Arg, AsyncCallback, object, IAsyncResult> begin, Func<IAsyncResult, R> end) { return Async<int>.BuildPrimitive(a, begin.ToTupledFastFunc(), end.ToFastFunc()); } public static Async<int> StartWorkflow = AsyncModule.async.Return(0); } |