Luke Hoban's Blog

June, 2009

Blog - Title

June, 2009

  • LukeH's WebLog

    ICFP Programming Contest 2009

    • 2 Comments

    This year’s ICFP Programming Contest starts today.  We’ve got a team participating, any other F# teams out there? 

    Last year, I posted an F# implementation of the 2006 ICFP contest problem, which was an amazing and complex set of puzzles inside a custom virtual machine.   Here’s a Silverlight version of that virtual machine, implemented on top of the Console sample I posted yesterday.

    Silverlight UM – ICFP 2006

    See http://boundvariable.org/ and my previous post for details of what’s running in this console.

    [Note: Copy/Paste is supported on some browsers, but not when embedded in the blog post.  Use this page instead.]

     

     /// Start the UM running on a background thread.
    /// The input and output functions will be called on the
    /// background thread, and block the execution of the UM
    static member Launch(binaryUri : Uri, input : Stream, output : Stream) =
    async {
    try
    let
    writer = new System.IO.StreamWriter(output, AutoFlush=true)
    let reader = new System.IO.StreamReader(input)
    do writer.WriteLine("Press <enter> to begin. Note: Will download a large UMIX image.")
    do reader.ReadLine() |> ignore
    let request = System.Net.WebRequest.Create(binaryUri)
    do writer.WriteLine(binaryUri)
    let! response = request.AsyncGetResponse()
    let stream = response.GetResponseStream()
    do writer.WriteLine("Downloading...")
    let progress(percent) = writer.WriteLine("Downloaded: "+percent.ToString()+"%")
    let! bytes = asyncReadAllBytesFromStreamWithProgress (stream, int response.ContentLength, progress)
    do
    writer.WriteLine("Booting...")
    let ee = UM(bytes, Func<int>(input.ReadByte), Action<byte>(output.WriteByte))
    ee.Run()
    with
    | e -> System.Diagnostics.Debug.WriteLine("UM failed with {0}", e)
    } |> Async.Start

    F# Async in Silverlight

    In Silverlight, the APIs for synchronous I/O have been removed, which forces developers to do the ‘right thing’ and make async calls for long-running I/O operations, thus (hopefully) keeping the UI responsive.  This makes F# a really nice implementation language for Silverlight applications, using F#’s async workflows.  In the code above, an asynchronous workflow describes the flow of control of launching the UM.  At the two points of long running network connections, async calls are made with “let!”, so that the UI thread is not blocked and the application remains responsive.  This ultimately hides a lot of complexity that would be added in chaining these asynchronous operations together in a standard .NET approach to this.

    Note also that the code above calls a helper function “asyncReadAllBytesFromStreamWithProgress”, which shows how async code in F# can be nicely factored using the same sort of “extract method” factoring you are used to in synchronous programming.  The implementation of this helper also shows that async calls with ‘let!’ can be placed inside ‘for’ and ‘while’ loops.  Chances are you don’t even want to try doing that with standard Begin/End calls!

    let internal asyncReadAllBytesFromStreamWithProgress(stream:Stream, length:int, progress:int->unit) =
    async {
    let offset = ref 0
    let count = ref length
    let buffer = Array.zeroCreate<byte> !count
    while !count > 0 do
    let!
    bytesRead = stream.AsyncRead(buffer, !offset, (min 524288 !count))
    if bytesRead = 0 then raise (new EndOfStreamException("Read beyond the EOF"))
    do offset := !offset + bytesRead
    do count := !count - bytesRead
    progress(100 * !offset / length)
    return buffer
    }

    Consuming F# from C#+XAML

    The majority of this app is written in F# – the console control is authored in F# and the application logic is in F#.  Here is the C# and XAML that ties it together:

    <UserControl xmlns:my="clr-namespace:System.Windows.Controls;assembly=SilverlightConsole"  x:Class="UM.Page"
    xmlns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml
    Width="600" Height="400">
    <
    Grid x:Name="LayoutRoot" Background="White">
    <
    my:SilverlightConsole x:Name="console" Width="600" Height="400"
    FontFamily="Courier New" FontSize="13" FontWeight="Bold"
    Foreground="GreenYellow" Background="Black" Text="" />
    </
    Grid>
    </
    UserControl>
    public partial class Page : UserControl {
    public Page() {
    InitializeComponent();
    Uri curPage = System.Windows.Application.Current.Host.Source;
    UriBuilder builder = new UriBuilder(curPage.Scheme, curPage.Host, curPage.Port, "umix.dll");
    ICFP.UM.Launch(builder.Uri, console.InputStream, console.OutputStream);
    }
    }

    Summary

    Check out ICFP Programming Contest  2009 this weekend, and take F#+Async+Silverlight for a spin.

  • LukeH's WebLog

    F# in Silverlight

    • 9 Comments

    Over the last couple years, there has been an explosion of interest in Silverlight.  As a .NET-based runtime, it is possible to compile Silverlight applications with any .NET language, and we’ve seen a lot of F# developers using F# in Silverlight.  However, until recently this involved building an application using the desktop version of the F# runtime, which could result in some pitfalls and mixed levels of success.

    With the recent F# May CTP though, we now provide a Silverlight version of the F# runtime, FSharp.Core.dll, along with the F# release.  This enables building truly first-class Silverlight components using F#.

    To make this easier, I’ve posted some Silverlight F# project templates and samples on Code Gallery. 

    Download

    F# Templates and Samples for Silverlight

    Templates

    image

    Samples

    L-Systems

    Lindenmayer Systems are an interesting way of generating a variety of fractals using a simple set of rewrite rules.  Check out the fascinating book The Algorithmic Beauty of Plants for details.  The Silverlight application below uses an L-System rewriter and rendered written in F#.

  • F, G, A, B are drawn as a move forward.
  • + is a turn right.
  • - is a turn right.
  • X, Y and anything else is skipped during rendering.

     

    open System.Windows
    open System.Windows.Shapes
    
    let rec internal applyRulesInOrder rules c =
        match rules with
        | [] -> string c
        | rule::rules' ->
            match rule c with
            | None -> applyRulesInOrder rules' c
            | Some result -> result
    
    let internal step rules current =
         current
         |> String.collect (applyRulesInOrder rules)
    
    let internal rotate (x,y) theta =
        let x' = x * cos theta - y * sin theta
        let y' = x * sin theta + y * cos theta
        (x',y')
    
    let rec internal render (x,y) (dx,dy) angle points system =
        match system with
        | [] -> (x,-y)::points
        | 'A'::system' | 'B'::system' | 'F'::system' | 'G'::system' ->
            let x',y' = x+dx,y+dy
            render (x',y') (dx,dy) angle ((x,-y)::points)  system'
        | '+'::system' ->
            let (dx',dy') = rotate (dx,dy) angle
            render (x,y) (dx',dy') angle points system'
        | '-'::system' ->
            let (dx',dy') = rotate (dx,dy) (-angle)
            render (x,y) (dx',dy') angle points system'
        | _::system' ->
            render (x,y) (dx,dy) angle points system'
    
    let rec internal applyN f n x =
        if n = 0 then x
        else f (applyN f (n-1) x)
    
    let internal normalize points =
        let minX = points |> Seq.map (fun (x,_) -> x) |> Seq.min
        let minY = points |> Seq.map (fun (_,y) -> y) |> Seq.min
        points |> List.map (fun (x,y) -> new Point(x-minX, y-minY))
    
    type LSystem(rulesString:string, start:string, angle:int, stepSize:int, n:int) =
        let expanded,isError =
            try
                let rules =
                    rulesString.Split([|"\r";"\n"|], System.StringSplitOptions.RemoveEmptyEntries)
                    |> Array.map (fun line -> line.Split([|"->"|], System.StringSplitOptions.RemoveEmptyEntries))
                    |> Array.map (fun fromAndTo -> (fromAndTo.[0].[0], fromAndTo.[1]))
                let ruleFunctions = [ for (c, s) in rules -> fun x -> if x = c then Some s else None]
                applyN (step ruleFunctions) n start, false
            with
            | e -> "", true
    
        member this.Render(polyline : Polyline) =
            let points = render (0.0,0.0) (float stepSize,0.0) (float angle * System.Math.PI / 180.0) [] (List.ofSeq expanded)
            for pt in normalize points do polyline.Points.Add(pt)
            isError
    
            

    Console Control

    A resuable Silverlight control providing a console emulation. The control exposes input and ouput streams akin to those on the System.Console class. Could be used to provide console input and output as part of a Silverlight application, or as a way to convert Windows Console apps to Silverlight apps.

    This samples hooks the Console up to a simple echo loop.

     

    namespace System.Windows.Controls
    
    open System
    open System.IO
    open System.Windows
    open System.Windows.Controls
    open System.Windows.Input
    open SilverlightContrib.Utilities.ClipboardHelper
    open System.Text
    
    // A shared base implementation of Stream for 
    // use by the console input and output streams
    [<AbstractClass>]
    type private ConsoleStream(isRead) = 
        inherit Stream()
        override this.CanRead = isRead
        override this.CanWrite = not isRead
        override this.CanSeek = false
        override this.Position
            with get() = raise (new NotSupportedException("Console stream does not have a position"))
            and  set(v) = raise (new NotSupportedException("Console stream does not have a position"))
        override this.Length = raise (new NotSupportedException("Console stream does not have a length"))
        override this.Flush() = ()
        override this.Seek(offset, origin) = raise (new NotSupportedException("Console stream cannot seek"))
        override this.SetLength(v) = raise (new NotSupportedException("Console stream does not have a length")) 
    
    /// A control representing a Console window
    /// Provides an InputStream and OutputStream
    /// for reading an writing character input.
    /// Also supports copy/paste on some browsers
    type SilverlightConsole() as self = 
        inherit TextBox()
    
        // The queue of user input which has been collected by the
        // console, but not yet read from the input stream
        let readQueue = new System.Collections.Generic.Queue<int>()
    
        // A stream that reads characters from user input
        let inputStream = 
            { new ConsoleStream(true) with
                override this.Write(buffer,offset,count) = 
                    raise (new NotSupportedException("Cannot write from Console input stream"))
                override this.Read(buffer,offset,count) = 
                    do System.Diagnostics.Debug.WriteLine("Starting to read {0} bytes", count)
                    let rec waitForAtLeastOneByte() =
                        let shouldSleep = ref true
                        let ret = ref [||]
                        lock readQueue  (fun () ->
                            shouldSleep := readQueue.Count < 1
                            if not !shouldSleep then 
                                let lengthToRead = min readQueue.Count count
                                ret := Array.init lengthToRead (fun i -> byte (readQueue.Dequeue())))
                        if !shouldSleep
                        then System.Threading.Thread.Sleep(100); waitForAtLeastOneByte()
                        else !ret
                    let bytes = waitForAtLeastOneByte()
                    System.Array.Copy(bytes, 0, buffer, offset, bytes.Length)
                    do System.Diagnostics.Debug.WriteLine("Finished reading {0} bytes", bytes.Length)
                    bytes.Length
            }
    
        // A stream that sends character output onto the console screen
        let outputStream =
            { new ConsoleStream(false) with
                override this.Read(buffer,offset,count) = 
                    raise (new NotSupportedException("Cannot read from Console output stream"))
                override this.Write(buffer,offset,count) = 
                    let isDelete = offset < 0
                    let newText =
                         if isDelete
                         then ""
                        else UnicodeEncoding.UTF8.GetString(buffer, offset, count)
                    let _ = self.Dispatcher.BeginInvoke(fun () ->
                        if isDelete then
                             if self.Text.Length >= count then
                                 self.Text <- self.Text.Substring(0, self.Text.Length - count)
                        else
                            do self.Text <- self.Text + newText
                        do self.SelectionStart <- self.Text.Length
                        do self.SelectionLength <- 0)
                    ()
            }
    
        let shiftNumbers = [|')';'!';'@';'#';'$';'%';'^';'&';'*';'('|]
        let currentInputLine = new System.Collections.Generic.List<int>()
    
        // Handles key down events
        // Processes the pressed key and turns it into console input
        // Also echos the pressed key to the console
        let keyDownHandler(keyArgs : KeyEventArgs) =
            try 
                do keyArgs.Handled <- true
                let shiftDown = Keyboard.Modifiers &&& ModifierKeys.Shift <> (enum 0)
                let ctrlDown = Keyboard.Modifiers &&& ModifierKeys.Control <> (enum 0)
                let p = keyArgs.PlatformKeyCode
                if ctrlDown || keyArgs.Key = Key.Ctrl then
                    if keyArgs.Key = Key.V then
                        lock currentInputLine (fun () ->
                            let clipboard = new ClipboardHelper()
                            let fromClipboard = clipboard.GetData()
                            for c in fromClipboard do
                                do currentInputLine.Add(int c)
                                outputStream.WriteByte(byte c)
                                if c = '\n' then
                                    for i in currentInputLine do
                                        do System.Diagnostics.Debug.WriteLine("Enqueued {0}", char i)
                                        do readQueue.Enqueue(i)
                                    do currentInputLine.Clear()
                        )
                    elif keyArgs.Key = Key.C then
                        let text = self.SelectedText
                        let clipboard = new ClipboardHelper()
                        clipboard.SetData(text)
                else
                    System.Diagnostics.Debug.WriteLine("Got key {0} {1} {2}", p, char p, keyArgs.Key)
                    let ascii =
                        match p with
                        | n when n >= 65 && n <= 90 -> if shiftDown then p else p+32
                        | n when n >= 48 && n <= 57 -> if shiftDown then int shiftNumbers.[p-48] else p
                        | 8 -> 8 // backspace
                        | 13 -> int '\n'
                        | 32 -> int ' '
                        | 186 -> if shiftDown then int ':' else int ';'
                        | 187 -> if shiftDown then int '+' else int '='
                        | 188 -> if shiftDown then int '<' else int ','
                        | 189 -> if shiftDown then int '_' else int '-'
                        | 190 -> if shiftDown then int '>' else int '.'
                        | 191 -> if shiftDown then int '?' else int '/'
                        | 192 -> if shiftDown then int '~' else int '`'
                        | 219 -> if shiftDown then int '{' else int '['
                        | 220 -> if shiftDown then int '|' else int '\\'
                        | 221 -> if shiftDown then int '}' else int ']'
                        | 222 -> if shiftDown then int '\"' else int '\''
                        | _ -> -1
                    if ascii = 8 then
                        lock currentInputLine (fun () ->
                            if currentInputLine.Count > 0 then currentInputLine.RemoveAt(currentInputLine.Count - 1)
                            outputStream.Write([||], -1, 1)
                        )
                    elif ascii > 0 then
                        lock currentInputLine (fun () ->
                            do currentInputLine.Add(ascii)
                            outputStream.WriteByte(byte ascii)
                        )
                    if ascii = int '\n' then
                         lock currentInputLine (fun () ->
                            for i in currentInputLine do
                                 do System.Diagnostics.Debug.WriteLine("Enqueued {0}", char i)
                                if i = 10 then
                                    do readQueue.Enqueue(13)
                                do readQueue.Enqueue(i)
                            do currentInputLine.Clear())
                    do self.SelectionStart <- self.Text.Length
                    do self.SelectionLength <- 0
            with
            | e -> System.Diagnostics.Debug.WriteLine(e)
    
        // Lazily initialized StreamReader/StreamWriter
        let outReader = lazy (new System.IO.StreamWriter(outputStream, Encoding.UTF8, 256, AutoFlush=true))
        let inReader = lazy (new System.IO.StreamReader(inputStream, Encoding.UTF8, false, 256))
    
        // Manually handle the Return key so we can accept newlines
        do self.AcceptsReturn <- true
        // Make sure a vertical scrollbar appears when needed
        do self.VerticalScrollBarVisibility <- ScrollBarVisibility.Auto
        // Make the control read-only so that users cannot move the cusor or change the contents
        // Unfortunatley, this also greys it out - ideally we could seperate theese two.
        do self.IsReadOnly <- true
        // Hookup the keyDownHandler
        do self.KeyDown.Add(keyDownHandler)
    
        /// The raw input stream for the Console
        member this.InputStream = inputStream :> Stream
        /// The raw ouput stream for the Console
        member this.OutputStream = outputStream :> Stream
    
        /// A StreamWriter for writing to the Console
        member this.Out = outReader.Value
        /// A StreamReader for reading from the Console
        member this.In = inReader.Value
    

    Summary

    Try out F# with Silverlight using the F# May CTP and the F# for Silverlight templates.

Page 1 of 1 (2 items)