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.
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
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 }
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); }}
Check out ICFP Programming Contest 2009 this weekend, and take F#+Async+Silverlight for a spin.
Hi Luke.
Thanks.
Much appreciated.
Look forward to your next post along these lines.
Art
Not sure if <Enter> works under Mac/FF/Silverlight. I could not get any response on the console.